From 418ed872fdf2dd60fd2e2fa82b43b7a14f6848e3 Mon Sep 17 00:00:00 2001 From: Maddy Miller Date: Sat, 25 Oct 2025 10:19:40 +0200 Subject: [PATCH 1/2] Use a separate tree registry for tree placement (#2829) * Use a separate tree registry for tree placement * Initialize the registries on the other platforms * Add the tree type system to the registry converter * Allow CoralTree features * Add 1.21.9 adapter changes * Apply PR feedback * Re-add existing Bukkit implementation (cherry picked from commit c561b0d96cec44c1038ff3b3b9d3e07baee963e5) Co-authored-by: SirYwell --- .../ext/fawe/v1_20_R2/PaperweightAdapter.java | 19 + .../fawe/v1_20_R2/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_20_R3/PaperweightAdapter.java | 19 + .../fawe/v1_20_R3/PaperweightFaweAdapter.java | 41 + .../ext.fawe/v1_20_R4/PaperweightAdapter.java | 19 + .../fawe/v1_20_R4/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_21_R1/PaperweightAdapter.java | 31 + .../fawe/v1_21_R1/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_21_11/PaperweightAdapter.java | 29 + .../fawe/v1_21_11/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_21_4/PaperweightAdapter.java | 28 + .../fawe/v1_21_4/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_21_5/PaperweightAdapter.java | 28 + .../fawe/v1_21_5/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_21_6/PaperweightAdapter.java | 29 + .../fawe/v1_21_6/PaperweightFaweAdapter.java | 41 + .../ext/fawe/v1_21_9/PaperweightAdapter.java | 29 + .../fawe/v1_21_9/PaperweightFaweAdapter.java | 41 + .../bukkit/adapter/FaweAdapter.java | 6 +- .../sk89q/worldedit/bukkit/BukkitWorld.java | 24 + .../worldedit/bukkit/WorldEditPlugin.java | 9 +- .../bukkit/adapter/BukkitImplAdapter.java | 15 + .../cli/schematic/ClipboardWorld.java | 5 +- .../core/wrappers/WorldWrapper.java | 7 + .../java/com/sk89q/worldedit/EditSession.java | 37 + .../worldedit/command/ApplyBrushCommands.java | 4 +- .../worldedit/command/BrushCommands.java | 4 +- .../worldedit/command/GenerationCommands.java | 2 +- .../worldedit/command/PaintBrushCommands.java | 4 +- .../worldedit/command/RegionCommands.java | 2 +- .../sk89q/worldedit/command/ToolCommands.java | 4 +- .../command/argument/RegistryConverter.java | 4 +- .../command/factory/TreeGeneratorFactory.java | 14 +- .../worldedit/command/tool/TreePlanter.java | 8 +- .../function/generator/ForestGenerator.java | 3 + .../function/generator/TreeGenerator.java | 48 ++ .../sk89q/worldedit/util/TreeGenerator.java | 2 + .../com/sk89q/worldedit/world/NullWorld.java | 3 +- .../java/com/sk89q/worldedit/world/World.java | 20 +- .../worldedit/world/generation/TreeType.java | 18 +- .../sk89q/worldedit/fabric/FabricWorld.java | 723 +++++++++++------ .../worldedit/fabric/FabricWorldEdit.java | 365 ++++++--- .../worldedit/neoforge/NeoForgeWorld.java | 765 ++++++++++++++++++ .../worldedit/neoforge/NeoForgeWorldEdit.java | 501 ++++++++++++ .../sk89q/worldedit/sponge/SpongeWorld.java | 527 ++++++++---- .../worldedit/sponge/SpongeWorldEdit.java | 473 +++++------ 46 files changed, 3394 insertions(+), 803 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java rename worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java => worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java (65%) create mode 100644 worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java create mode 100644 worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java 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 28391ee583..ce2332b925 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 @@ -68,10 +68,12 @@ 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.generation.TreeType; 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.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -113,6 +115,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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -918,6 +923,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { 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 dadd3f1139..46d7d0b93e 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 @@ -19,6 +19,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -51,6 +52,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -88,6 +90,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -679,6 +682,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java index f524676068..f4562764bc 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java @@ -68,10 +68,12 @@ 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.generation.TreeType; 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.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -113,6 +115,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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -917,6 +922,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java index c622b25cb9..3b69d37248 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java @@ -19,6 +19,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -50,6 +51,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -87,6 +89,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -678,6 +681,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java index cb6da03c34..47785f4a9e 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java @@ -68,10 +68,12 @@ 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.generation.TreeType; 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.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -117,6 +119,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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -941,6 +946,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java index 44bb4c6ec5..3526440b07 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -52,6 +53,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -92,6 +94,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -692,6 +695,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java index 5e8ed03d7e..6f6cd435a2 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java @@ -29,6 +29,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -68,11 +69,13 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentPatch; @@ -117,6 +120,10 @@ 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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -927,6 +934,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { @@ -957,6 +978,16 @@ public void sendBiomeUpdates(World world, Iterable chunks) { } @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature k = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE) + .getOrThrow(ResourceKey.create(Registries.PLACED_FEATURE, ResourceLocation.tryParse(treeType.id()))) + .value(); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + WorldGenLevel proxyLevel = PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this); + return k != null && k.place(proxyLevel, chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature k = originalWorld.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java index 033d2983c0..e6a542a607 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -52,6 +53,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -92,6 +94,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -693,6 +696,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java index 57e5a6cc8a..f1d65c8da8 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java @@ -70,6 +70,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; @@ -130,6 +131,10 @@ 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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -902,6 +907,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (Identifier name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -920,6 +938,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(Identifier.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(Identifier.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java index 77c772a452..c9bc10e856 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java @@ -18,6 +18,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -50,6 +51,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -90,6 +92,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -659,6 +662,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(Identifier.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java index 01987f18cb..70fbe27937 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java @@ -65,6 +65,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -112,6 +113,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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -879,6 +883,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -908,6 +925,17 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java index f1282259a1..5aeba33a96 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java @@ -18,6 +18,7 @@ import com.google.common.collect.Sets; import com.mojang.serialization.Codec; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -50,6 +51,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -91,6 +93,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -671,6 +674,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java index 7dfb944b6c..0d95e3fa40 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java @@ -66,6 +66,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -126,6 +127,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.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -891,6 +895,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -909,6 +926,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java index d9be2690fc..9db33d44a3 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java @@ -19,6 +19,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -51,6 +52,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -91,6 +93,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -665,6 +668,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java index 20acb6a704..eb3b0b6d20 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java @@ -69,6 +69,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -130,6 +131,10 @@ 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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -914,6 +919,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -932,6 +950,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java index 7c663390c0..a09c96b7a8 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java @@ -19,6 +19,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -51,6 +52,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -91,6 +93,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -681,6 +684,44 @@ public boolean canTransformBlocks() { return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java index 17898f34f4..78b86d485b 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java @@ -69,6 +69,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -129,6 +130,10 @@ 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.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -911,6 +916,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -929,6 +947,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java index 2bde85f600..e39b875476 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java @@ -19,6 +19,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -51,6 +52,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -91,6 +93,7 @@ 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.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -675,6 +678,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java index b902696d2f..66a229fdb0 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java @@ -13,7 +13,6 @@ import org.bukkit.World; import org.bukkit.block.BlockState; -import java.util.Collection; import java.util.List; import java.util.Map; @@ -35,6 +34,11 @@ protected FaweAdapter(final BukkitImplAdapter parent) { this.parent = parent; } + @Override + public void initializeRegistries() { + parent.initializeRegistries(); + } + @Override public boolean generateTree( final TreeGenerator.TreeType treeType, 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 99aaefdd12..40121aa912 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 @@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -315,10 +316,16 @@ public boolean clearContainerBlockContents(BlockVector3 pt) { /** * An EnumMap that stores which WorldEdit TreeTypes apply to which Bukkit TreeTypes. */ + @Deprecated private static final EnumMap treeTypeMapping = new EnumMap<>(TreeGenerator.TreeType.class); static { + generateTreeMap(); + } + + @SuppressWarnings("deprecation") + private static void generateTreeMap() { for (TreeGenerator.TreeType type : TreeGenerator.TreeType.values()) { try { TreeType bukkitType = TreeType.valueOf(type.name()); @@ -348,10 +355,13 @@ public boolean clearContainerBlockContents(BlockVector3 pt) { } } + @Deprecated public static TreeType toBukkitTreeType(TreeGenerator.TreeType type) { return treeTypeMapping.get(type); } + @SuppressWarnings("deprecation") + @Deprecated @Override public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 pt) { //FAWE start - allow tree commands to be undone and obey region restrictions @@ -360,6 +370,20 @@ public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession //FAWE end } + @Override + public boolean generateTree( + final com.sk89q.worldedit.world.generation.TreeType type, + final EditSession editSession, + final BlockVector3 position + ) throws MaxChangedBlocksException { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + if (adapter != null) { + return adapter.generateTree(type, getWorld(), editSession, position); + } + // No adapter, we can't generate this. + return false; + } + @Override public void dropItem(Vector3 pt, BaseItemStack item) { World world = getWorld(); 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 d0708887b0..14b5d019b4 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 @@ -259,7 +259,6 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new AsyncTabCompleteListener(), this); } - initializeRegistries(); // this creates the objects matching Bukkit's enums - but doesn't fill them with data yet if (Bukkit.getWorlds().isEmpty()) { setupPreWorldData(); // register this so we can load world-dependent data right as the first world is loading @@ -291,6 +290,7 @@ public void onEnable() { private void setupPreWorldData() { loadAdapter(); + initializeRegistries(); // this creates the objects matching Bukkit's enums - but doesn't fill them with data yet WorldEdit.getInstance().loadMappings(); } @@ -352,6 +352,13 @@ private void initializeRegistries() { EntityType.REGISTRY.register("minecraft:" + lowerCaseMcId, new EntityType("minecraft:" + lowerCaseMcId)); } } + + // Registries only available via NMS + BukkitImplAdapter adapter = getBukkitImplAdapter(); + if (adapter != null) { + adapter.initializeRegistries(); + } + // ... :| GameModes.get(""); WeatherTypes.get(""); 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 f3db4cb9f3..2fe28534b6 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 @@ -31,6 +31,7 @@ import com.sk89q.jnbt.LinBusConverter; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -53,6 +54,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import org.bukkit.Keyed; @@ -330,6 +332,19 @@ default void sendBiomeUpdates(World world, Iterable chunks) { } + /** + * Generates a Minecraft tree at the given location. + * + * @param treeType The tree + * @param world The world + * @param session The EditSession + * @param pt The location + * @return If it succeeded + */ + default boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + throw new UnsupportedOperationException("This adapter does not support generating features."); + } + /** * Generates a Minecraft feature at the given location. * diff --git a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java index 1502fdbf38..e838baee4a 100644 --- a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java +++ b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java @@ -41,13 +41,13 @@ import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.world.AbstractWorld; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; 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.generation.TreeType; import javax.annotation.Nullable; import java.io.File; @@ -141,8 +141,7 @@ public boolean regenerate(Region region, Extent extent, RegenOptions options) { } @Override - public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) - throws MaxChangedBlocksException { + public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { return false; } 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 8421df551c..bd443ff478 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 @@ -39,6 +39,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; @@ -290,6 +291,12 @@ public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession } } + @Override + public boolean generateTree(final TreeType type, final EditSession editSession, final BlockVector3 position) throws + MaxChangedBlocksException { + return parent.generateTree(type, editSession, position); + } + @Override public boolean generateStructure(final StructureType type, final EditSession editSession, final BlockVector3 position) { return parent.generateStructure(type, editSession, position); 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 29c3e26032..330d2b6678 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -150,6 +150,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.registry.LegacyMapper; import org.apache.logging.log4j.Logger; @@ -3033,7 +3034,9 @@ public int makePumpkinPatches(BlockVector3 position, int apothem, double density * @param treeType the tree type * @return number of trees created * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link #makeForest(Region, double, TreeType)}. */ + @Deprecated public int makeForest(BlockVector3 basePosition, int size, double density, TreeGenerator.TreeType treeType) throws MaxChangedBlocksException { return makeForest(CuboidRegion.fromCenter(basePosition, size), density, treeType); @@ -3047,7 +3050,9 @@ public int makeForest(BlockVector3 basePosition, int size, double density, TreeG * @param treeType the tree type * @return number of trees created * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link #makeForest(Region, double, TreeType)}. */ + @Deprecated public int makeForest(Region region, double density, TreeGenerator.TreeType treeType) throws MaxChangedBlocksException { ForestGenerator generator = new ForestGenerator(this, treeType); GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); @@ -3059,6 +3064,38 @@ public int makeForest(Region region, double density, TreeGenerator.TreeType tree return ground.getAffected(); } + /** + * Makes a forest. + * + * @param basePosition a position + * @param size a size + * @param density between 0 and 1, inclusive + * @param treeType the tree type + * @return number of trees created + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeForest(BlockVector3 basePosition, int size, double density, TreeType treeType) throws MaxChangedBlocksException { + return makeForest(CuboidRegion.fromCenter(basePosition, size), density, treeType); + } + + /** + * Makes a forest. + * + * @param region the region to generate trees in + * @param density between 0 and 1, inclusive + * @param treeType the tree type + * @return number of trees created + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeForest(Region region, double density, TreeType treeType) throws MaxChangedBlocksException { + com.sk89q.worldedit.function.generator.TreeGenerator generator = new com.sk89q.worldedit.function.generator.TreeGenerator(this, treeType); + GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); + LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground); + visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); + Operations.completeLegacy(visitor); + return ground.getAffected(); + } + /** * Get the block distribution inside a region. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java index 66e801ae2d..ba5f095a84 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java @@ -40,8 +40,8 @@ import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.regions.factory.RegionFactory; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandParameters; @@ -115,7 +115,7 @@ public void forest( CommandParameters parameters, Player player, LocalSession localSession, @Arg(desc = "The type of tree to plant") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setApplyBrush(parameters, player, localSession, new TreeGeneratorFactory(type)); } 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 e1a2e3e401..1042932e9b 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 @@ -117,7 +117,6 @@ import com.sk89q.worldedit.regions.factory.SphereRegionFactory; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.HandSide; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; @@ -128,6 +127,7 @@ 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.generation.TreeType; import org.anarres.parallelgzip.ParallelGZIPOutputStream; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; @@ -1189,7 +1189,7 @@ public void forest( @Arg(desc = "The density of the brush", def = "20") double density, @Arg(desc = "The type of tree to use") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setOperationBasedBrush(player, localSession, radius, new Paint(new TreeGeneratorFactory(type), density / 100), shape, "worldedit.brush.forest" 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 3206524baf..d949859837 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 @@ -53,12 +53,12 @@ import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java index c34c66055a..f2f276cde0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java @@ -39,9 +39,9 @@ import com.sk89q.worldedit.internal.annotation.Direction; import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.regions.factory.RegionFactory; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandParameters; @@ -130,7 +130,7 @@ public void forest( CommandParameters parameters, Player player, LocalSession localSession, @Arg(desc = "The type of tree to plant") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setPaintBrush(parameters, player, localSession, new TreeGeneratorFactory(type)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 197968bb36..57432cae58 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -68,13 +68,13 @@ import com.sk89q.worldedit.regions.RegionOperationException; import com.sk89q.worldedit.regions.Regions; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; 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 1e63b947e5..83e712e3e1 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 @@ -51,7 +51,6 @@ import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.util.HandSide; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; @@ -59,6 +58,7 @@ 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.generation.TreeType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandMetadata; @@ -242,7 +242,7 @@ public void inspectBrush(Player player, LocalSession session) throws WorldEditEx public void tree( Player player, LocalSession session, @Arg(desc = "Type of tree to generate", def = "tree") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setTool(player, session, new TreePlanter(type), "worldedit.tool.tree.equip"); } 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 c9b69cc30c..2c4107a386 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 @@ -34,6 +34,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.weather.WeatherType; @@ -66,7 +67,8 @@ public static void register(CommandManager commandManager) { GameMode.class, WeatherType.class, ConfiguredFeatureType.class, - StructureType.class + StructureType.class, + TreeType.class ) .stream() .map(c -> (Class) c) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java index 1e6b06d7bc..421b5c675e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java @@ -22,20 +22,20 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.function.Contextual; import com.sk89q.worldedit.function.EditContext; -import com.sk89q.worldedit.function.generator.ForestGenerator; -import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.function.generator.TreeGenerator; +import com.sk89q.worldedit.world.generation.TreeType; -public final class TreeGeneratorFactory implements Contextual { +public final class TreeGeneratorFactory implements Contextual { - private final TreeGenerator.TreeType type; + private final TreeType type; - public TreeGeneratorFactory(TreeGenerator.TreeType type) { + public TreeGeneratorFactory(TreeType type) { this.type = type; } @Override - public ForestGenerator createFromContext(EditContext input) { - return new ForestGenerator((EditSession) input.getDestination(), type); + public TreeGenerator createFromContext(EditContext input) { + return new TreeGenerator((EditSession) input.getDestination(), type); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java index db92f6af91..e5740dcb5a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java @@ -30,7 +30,7 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.world.generation.TreeType; import javax.annotation.Nullable; @@ -39,9 +39,9 @@ */ public class TreePlanter implements BlockTool { - private final TreeGenerator.TreeType treeType; + private final TreeType treeType; - public TreePlanter(TreeGenerator.TreeType treeType) { + public TreePlanter(TreeType treeType) { this.treeType = treeType; } @@ -66,7 +66,7 @@ public boolean actPrimary( final BlockVector3 pos = clicked.toVector().add(0, 1, 0).toBlockPoint(); for (int i = 0; i < 10; i++) { - if (treeType.generate(editSession, pos)) { + if (player.getWorld().generateTree(treeType, editSession, pos)) { successful = true; break; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java index a042fd16c1..72b763cd18 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java @@ -31,7 +31,10 @@ /** * Generates forests by searching for the ground starting from the given upper Y * coordinate for every column given. + * + * @deprecated Use {@link com.sk89q.worldedit.function.generator.TreeGenerator} instead. */ +@Deprecated public class ForestGenerator implements RegionFunction { private final TreeGenerator.TreeType treeType; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java new file mode 100644 index 0000000000..35809a9187 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java @@ -0,0 +1,48 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.generator; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.generation.TreeType; + +public final class TreeGenerator implements RegionFunction { + + private final TreeType treeType; + private final EditSession editSession; + + /** + * Create a new instance. + * + * @param editSession the edit session + * @param treeType the tree type + */ + public TreeGenerator(EditSession editSession, TreeType treeType) { + this.editSession = editSession; + this.treeType = treeType; + } + + @Override + public boolean apply(BlockVector3 position) throws WorldEditException { + return editSession.getWorld().generateTree(treeType, editSession, position); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java index 2b6bae6b73..68ce6f4c1e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java @@ -41,8 +41,10 @@ /** * Tree generator. */ +@Deprecated public final class TreeGenerator { + @Deprecated public enum TreeType { TREE("Oak tree", "oak", "tree", "regular"), BIG_TREE("Large oak tree", "largeoak", "bigoak", "big", "bigtree"), diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java index d3ad7bfd92..3832f50a29 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java @@ -38,7 +38,6 @@ import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BaseBlock; @@ -143,7 +142,7 @@ public boolean regenerate(Region region, EditSession editSession) { } @Override - public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { + public boolean generateTree(com.sk89q.worldedit.world.generation.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { return false; } 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 8c9efb5bda..2430e705f8 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 @@ -48,6 +48,7 @@ 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.generation.TreeType; import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; @@ -309,9 +310,24 @@ default boolean regenerate(Region region, Extent extent, RegenOptions options) { * @param position the position * @return true if generation was successful * @throws MaxChangedBlocksException thrown if too many blocks were changed + * @deprecated Use {@link #generateTree(TreeType, EditSession, BlockVector3)} instead */ - boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws - MaxChangedBlocksException; + @Deprecated + default boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws + MaxChangedBlocksException { + return false; + } + + /** + * Generate a tree at the given position. + * + * @param type the tree type + * @param editSession the {@link EditSession} + * @param position the position + * @return true if generation was successful + * @throws MaxChangedBlocksException thrown if too many blocks were changed + */ + boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException; /** * Generate a structure at the given position diff --git a/worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java similarity index 65% rename from worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java rename to worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java index df6b33b7b7..27dfd646c6 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java @@ -17,19 +17,17 @@ * along with this program. If not, see . */ -package com.sk89q.worldedit.bukkit; +package com.sk89q.worldedit.world.generation; -import com.sk89q.worldedit.util.TreeGenerator; -import org.junit.jupiter.api.Test; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.NamespacedRegistry; -import static org.junit.jupiter.api.Assertions.assertNotNull; +public record TreeType(String id) implements Keyed { -public class BukkitWorldTest { + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("tree_type", "minecraft"); - public void testTreeTypeMapping() { - for (TreeGenerator.TreeType type : TreeGenerator.TreeType.values()) { - assertNotNull(BukkitWorld.toBukkitTreeType(type), "No mapping for: " + type); - } + @Override + public String toString() { + return this.id; } - } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java index e93ae036ef..9f8ea90c44 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java @@ -19,14 +19,14 @@ package com.sk89q.worldedit.fabric; -import static com.google.common.base.Preconditions.checkNotNull; - import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import com.google.common.io.Files; -import com.sk89q.jnbt.CompoundTag; +import com.google.common.collect.Streams; +import com.google.common.util.concurrent.Futures; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; @@ -34,102 +34,123 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.fabric.internal.ExtendedMinecraftServer; +import com.sk89q.worldedit.fabric.internal.FabricEntity; +import com.sk89q.worldedit.fabric.internal.FabricServerLevelDelegateProxy; import com.sk89q.worldedit.fabric.internal.FabricWorldNativeAccess; import com.sk89q.worldedit.fabric.internal.NBTConverter; +import com.sk89q.worldedit.function.mask.AbstractExtentMask; +import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.Constants; -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; -import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.io.file.SafeFiles; import com.sk89q.worldedit.world.AbstractWorld; +import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; 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.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.block.LeavesBlock; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.ItemEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemUsageContext; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.placement.EndPlacements; +import net.minecraft.data.worldgen.placement.TreePlacements; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.WorldGenerationProgressListener; -import net.minecraft.server.world.ServerChunkManager; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Clearable; -import net.minecraft.util.Hand; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.world.World; -import net.minecraft.world.WorldSaveHandler; -import net.minecraft.world.biome.DefaultBiomeFeatures; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkManager; -import net.minecraft.world.chunk.ChunkStatus; -import net.minecraft.world.chunk.WorldChunk; -import net.minecraft.world.gen.feature.BirchTreeFeature; -import net.minecraft.world.gen.feature.DarkOakTreeFeature; -import net.minecraft.world.gen.feature.DefaultFeatureConfig; -import net.minecraft.world.gen.feature.Feature; -import net.minecraft.world.gen.feature.FeatureConfig; -import net.minecraft.world.gen.feature.HugeBrownMushroomFeature; -import net.minecraft.world.gen.feature.HugeRedMushroomFeature; -import net.minecraft.world.gen.feature.JungleGroundBushFeature; -import net.minecraft.world.gen.feature.JungleTreeFeature; -import net.minecraft.world.gen.feature.LargeOakTreeFeature; -import net.minecraft.world.gen.feature.MegaJungleTreeFeature; -import net.minecraft.world.gen.feature.MegaPineTreeFeature; -import net.minecraft.world.gen.feature.OakTreeFeature; -import net.minecraft.world.gen.feature.PineTreeFeature; -import net.minecraft.world.gen.feature.PlantedFeatureConfig; -import net.minecraft.world.gen.feature.SavannaTreeFeature; -import net.minecraft.world.gen.feature.SpruceTreeFeature; -import net.minecraft.world.gen.feature.SwampTreeFeature; -import net.minecraft.world.level.LevelProperties; - -import java.io.File; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProblemReporter; +import net.minecraft.util.RandomSource; +import net.minecraft.world.Clearable; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +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.LiquidBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +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.DerivedLevelData; +import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import org.enginehub.linbus.tree.LinCompoundTag; + import java.lang.ref.WeakReference; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.OptionalInt; -import java.util.Random; +import java.util.OptionalLong; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.Collectors; - import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + /** * An adapter to Minecraft worlds for WorldEdit. */ public class FabricWorld extends AbstractWorld { - private static final Random random = new Random(); - private static final int UPDATE = 1, NOTIFY = 2; + private static final RandomSource random = RandomSource.create(); - private static final net.minecraft.block.BlockState JUNGLE_LOG = Blocks.JUNGLE_LOG.getDefaultState(); - private static final net.minecraft.block.BlockState JUNGLE_LEAF = Blocks.JUNGLE_LEAVES.getDefaultState().with(LeavesBlock.PERSISTENT, Boolean.TRUE); - private static final net.minecraft.block.BlockState JUNGLE_SHRUB = Blocks.OAK_LEAVES.getDefaultState().with(LeavesBlock.PERSISTENT, Boolean.TRUE); + private static ResourceLocation getDimensionRegistryKey(Level world) { + return Objects.requireNonNull(world.getServer(), "server cannot be null") + .registryAccess() + .lookupOrThrow(Registries.DIMENSION_TYPE) + .getKey(world.dimensionType()); + } - private final WeakReference worldRef; + private final WeakReference worldRef; private final FabricWorldNativeAccess worldNativeAccess; /** @@ -137,35 +158,20 @@ public class FabricWorld extends AbstractWorld { * * @param world the world */ - FabricWorld(World world) { + FabricWorld(Level world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); this.worldNativeAccess = new FabricWorldNativeAccess(worldRef); } - /** - * Get the underlying handle to the world. - * - * @return the world - * @throws WorldEditException thrown if a reference to the world was lost (i.e. world was unloaded) - */ - public World getWorldChecked() throws WorldEditException { - World world = worldRef.get(); - if (world != null) { - return world; - } else { - throw new WorldReferenceLostException("The reference to the world was lost (i.e. the world may have been unloaded)"); - } - } - /** * Get the underlying handle to the world. * * @return the world * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded) */ - public World getWorld() { - World world = worldRef.get(); + public Level getWorld() { + Level world = worldRef.get(); if (world != null) { return world; } else { @@ -175,32 +181,31 @@ public World getWorld() { @Override public String getName() { - return getWorld().getLevelProperties().getLevelName(); + LevelData levelProperties = getWorld().getLevelData(); + return ((ServerLevelData) levelProperties).getLevelName(); } @Override - public String getId() { - return getWorld().getLevelProperties().getLevelName() - .replace(" ", "_").toLowerCase(Locale.ROOT) - + getWorld().dimension.getType().getSuffix(); + public String id() { + return getName() + "_" + getDimensionRegistryKey(getWorld()); } @Override public Path getStoragePath() { - final World world = getWorld(); - if (world instanceof ServerWorld) { - return ((ServerWorld) world).getSaveHandler().getWorldDir().toPath(); - } - return null; + final Level world = getWorld(); + MinecraftServer server = world.getServer(); + checkState(server instanceof ExtendedMinecraftServer, "Need a server world"); + return ((ExtendedMinecraftServer) server).getStoragePath(world); } @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { + clearContainerBlockContents(position); return worldNativeAccess.setBlock(position, block, sideEffects); } @Override - public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { + public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); return Sets.intersection(FabricWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); } @@ -208,69 +213,81 @@ public Set applySideEffects(BlockVector3 position, BlockState previo @Override public int getBlockLightLevel(BlockVector3 position) { checkNotNull(position); - return getWorld().getLightLevel(FabricAdapter.toBlockPos(position)); + return getWorld().getMaxLocalRawBrightness(FabricAdapter.toBlockPos(position)); } @Override public boolean clearContainerBlockContents(BlockVector3 position) { checkNotNull(position); + BlockEntity tile = getWorld().getBlockEntity(FabricAdapter.toBlockPos(position)); if ((tile instanceof Clearable)) { - ((Clearable) tile).clear(); + ((Clearable) tile).clearContent(); return true; } return false; } @Override - public BiomeType getBiome(BlockVector2 position) { + public BiomeType getBiome(BlockVector3 position) { checkNotNull(position); - return FabricAdapter.adapt(getWorld().getBiome(new BlockPos(position.getBlockX(), 0, position.getBlockZ()))); + ChunkAccess chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); + return getBiomeInChunk(position, chunk); + } + + private BiomeType getBiomeInChunk(BlockVector3 position, ChunkAccess chunk) { + return FabricAdapter.adapt( + chunk.getNoiseBiome(position.x() >> 2, position.y() >> 2, position.z() >> 2).value() + ); } @Override - public boolean setBiome(BlockVector2 position, BiomeType biome) { + public boolean setBiome(BlockVector3 position, BiomeType biome) { checkNotNull(position); checkNotNull(biome); - Chunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4, ChunkStatus.FULL, false); - if (chunk == null) { - return false; - } - chunk.getBiomeArray()[((position.getBlockZ() & 0xF) << 4 | position.getBlockX() & 0xF)] = FabricAdapter.adapt(biome); + ChunkAccess chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); + // Screw it, we know it's really mutable... + var biomeArray = (PalettedContainer>) chunk.getSection(chunk.getSectionIndex(position.y())).getBiomes(); + biomeArray.getAndSetUnchecked( + position.x() & 3, position.y() & 3, position.z() & 3, + getWorld().registryAccess().lookup(Registries.BIOME) + .orElseThrow() + .getOrThrow(ResourceKey.create(Registries.BIOME, ResourceLocation.parse(biome.id()))) + ); + chunk.markUnsaved(); return true; } - private static final LoadingCache fakePlayers - = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(WorldEditFakePlayer::new)); + private static final LoadingCache fakePlayers + = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(FabricFakePlayer::new)); @Override public boolean useItem(BlockVector3 position, BaseItem item, Direction face) { - ItemStack stack = FabricAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtData(), 1)); - ServerWorld world = (ServerWorld) getWorld(); - final WorldEditFakePlayer fakePlayer; + ItemStack stack = FabricAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtReference(), 1)); + ServerLevel world = (ServerLevel) getWorld(); + final FabricFakePlayer fakePlayer; try { fakePlayer = fakePlayers.get(world); } catch (ExecutionException ignored) { return false; } - fakePlayer.setStackInHand(Hand.MAIN_HAND, stack); - fakePlayer.setPositionAndAngles(position.getBlockX(), position.getBlockY(), position.getBlockZ(), + fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); + fakePlayer.absSnapTo(position.x(), position.y(), position.z(), (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); final BlockPos blockPos = FabricAdapter.toBlockPos(position); final BlockHitResult rayTraceResult = new BlockHitResult(FabricAdapter.toVec3(position), FabricAdapter.adapt(face), blockPos, false); - ItemUsageContext itemUseContext = new ItemUsageContext(fakePlayer, Hand.MAIN_HAND, rayTraceResult); - ActionResult used = stack.useOnBlock(itemUseContext); - if (used != ActionResult.SUCCESS) { + UseOnContext itemUseContext = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); + InteractionResult used = stack.useOn(itemUseContext); + if (used != InteractionResult.SUCCESS) { // try activating the block - if (getWorld().getBlockState(blockPos).activate(world, fakePlayer, Hand.MAIN_HAND, rayTraceResult)) { - used = ActionResult.SUCCESS; - } else { - used = stack.getItem().use(world, fakePlayer, Hand.MAIN_HAND).getResult(); - } + used = getWorld().getBlockState(blockPos).useItemOn(stack, world, fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); + } + if (used != InteractionResult.SUCCESS) { + used = stack.use(world, fakePlayer, InteractionHand.MAIN_HAND); } - return used == ActionResult.SUCCESS; + return used == InteractionResult.SUCCESS; } @Override @@ -282,97 +299,266 @@ public void dropItem(Vector3 position, BaseItemStack item) { return; } - ItemEntity entity = new ItemEntity(getWorld(), position.getX(), position.getY(), position.getZ(), FabricAdapter.adapt(item)); - entity.setPickupDelay(10); - getWorld().spawnEntity(entity); + ItemEntity entity = new ItemEntity(getWorld(), position.x(), position.y(), position.z(), FabricAdapter.adapt(item)); + entity.setPickUpDelay(10); + getWorld().addFreshEntity(entity); } @Override public void simulateBlockMine(BlockVector3 position) { BlockPos pos = FabricAdapter.toBlockPos(position); - getWorld().breakBlock(pos, true); + getWorld().destroyBlock(pos, true); } @Override - public boolean regenerate(Region region, EditSession editSession) { + public boolean canPlaceAt(BlockVector3 position, BlockState blockState) { + return FabricAdapter.adapt(blockState).canSurvive(getWorld(), FabricAdapter.toBlockPos(position)); + } + + @Override + public boolean regenerate(Region region, Extent extent, RegenOptions options) { // Don't even try to regen if it's going to fail. - ChunkManager provider = getWorld().getChunkManager(); - if (!(provider instanceof ServerChunkManager)) { + ChunkSource provider = getWorld().getChunkSource(); + if (!(provider instanceof ServerChunkCache)) { return false; } - File saveFolder = Files.createTempDir(); - // register this just in case something goes wrong - // normally it should be deleted at the end of this method - saveFolder.deleteOnExit(); try { - ServerWorld originalWorld = (ServerWorld) getWorld(); - - MinecraftServer server = originalWorld.getServer(); - WorldSaveHandler saveHandler = new WorldSaveHandler(saveFolder, originalWorld.getSaveHandler().getWorldDir().getName(), server, server.getDataFixer()); - World freshWorld = new ServerWorld(server, server.getWorkerExecutor(), saveHandler, originalWorld.getLevelProperties(), - originalWorld.dimension.getType(), originalWorld.getProfiler(), new NoOpChunkStatusListener()); - - // Pre-gen all the chunks - // We need to also pull one more chunk in every direction - CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 0, 16), region.getMaximumPoint().add(16, 0, 16)); - for (BlockVector2 chunk : expandedPreGen.getChunks()) { - freshWorld.getChunk(chunk.getBlockX(), chunk.getBlockZ()); - } + doRegen(region, extent, options); + } catch (Exception e) { + throw new IllegalStateException("Regen failed", e); + } + + return true; + } - FabricWorld from = new FabricWorld(freshWorld); - for (BlockVector3 vec : region) { - editSession.setBlock(vec, from.getFullBlock(vec)); + private void doRegen(Region region, Extent extent, RegenOptions options) throws Exception { + Path tempDir = Files.createTempDirectory("WorldEditWorldGen"); + LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); + try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("WorldEditTempGen")) { + ServerLevel originalWorld = (ServerLevel) getWorld(); + PrimaryLevelData levelProperties = getPrimaryLevelData(originalWorld.getLevelData()); + WorldOptions originalOpts = levelProperties.worldGenOptions(); + + long seed = options.getSeed().orElse(originalWorld.getSeed()); + levelProperties.worldOptions = options.getSeed().isPresent() + ? originalOpts.withSeed(OptionalLong.of(seed)) + : originalOpts; + + ResourceKey worldRegKey = originalWorld.dimension(); + try (ServerLevel serverWorld = new ServerLevel( + originalWorld.getServer(), Util.backgroundExecutor(), session, + ((ServerLevelData) originalWorld.getLevelData()), + worldRegKey, + new LevelStem( + originalWorld.dimensionTypeRegistration(), + originalWorld.getChunkSource().getGenerator() + ), + originalWorld.isDebug(), + seed, + // No spawners are needed for this world. + ImmutableList.of(), + // This controls ticking, we don't need it so set it to false. + false, + originalWorld.getRandomSequences() + )) { + regenForWorld(region, extent, serverWorld, options); + + // drive the server executor until all tasks are popped off + while (originalWorld.getServer().pollTask()) { + Thread.yield(); + } + } finally { + levelProperties.worldOptions = originalOpts; } - } catch (MaxChangedBlocksException e) { - throw new RuntimeException(e); } finally { - saveFolder.delete(); + SafeFiles.tryHardToDeleteDir(tempDir); } + } - return true; + private static PrimaryLevelData getPrimaryLevelData(LevelData levelData) { + if (levelData instanceof DerivedLevelData derivedLevelData) { + return getPrimaryLevelData(derivedLevelData.wrapped); + } else if (levelData instanceof PrimaryLevelData primaryLevelData) { + return primaryLevelData; + } else { + throw new IllegalStateException("Unknown level data type: " + levelData.getClass()); + } } + private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, + RegenOptions options) throws WorldEditException { + List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); + + // drive executor until loading finishes + serverWorld.getChunkSource().mainThreadProcessor + .managedBlock(() -> { + // bail out early if a future fails + if (chunkLoadings.stream().anyMatch(ftr -> + ftr.isDone() && Futures.getUnchecked(ftr) == null + )) { + return false; + } + return chunkLoadings.stream().allMatch(CompletableFuture::isDone); + }); + + Map chunks = new HashMap<>(); + for (CompletableFuture future : chunkLoadings) { + @Nullable + ChunkAccess chunk = future.getNow(null); + checkState(chunk != null, "Failed to generate a chunk, regen failed."); + chunks.put(chunk.getPos(), chunk); + } + + for (BlockVector3 vec : region) { + BlockPos pos = FabricAdapter.toBlockPos(vec); + ChunkAccess chunk = chunks.get(new ChunkPos(pos)); + BlockStateHolder state = FabricAdapter.adapt(chunk.getBlockState(pos)); + BlockEntity blockEntity = chunk.getBlockEntity(pos); + if (blockEntity != null) { + var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); + blockEntity.saveWithId(tagValueOutput); + net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); + state = state.toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(tag))); + } + extent.setBlock(vec, state.toBaseBlock()); + + if (options.shouldRegenBiomes()) { + BiomeType biome = getBiomeInChunk(vec, chunk); + extent.setBiome(vec, biome); + } + } + } + + private List> submitChunkLoadTasks(Region region, ServerLevel world) { + List> chunkLoadings = new ArrayList<>(); + // Pre-gen all the chunks + for (BlockVector2 chunk : region.getChunks()) { + chunkLoadings.add( + world.getChunkSource().getChunkFuture(chunk.x(), chunk.z(), ChunkStatus.FEATURES, true) + .thenApply(either -> either.orElse(null)) + ); + } + return chunkLoadings; + } + + @SuppressWarnings("deprecation") @Nullable - private static Feature createTreeFeatureGenerator(TreeType type) { - switch (type) { - case TREE: return new OakTreeFeature(DefaultFeatureConfig::deserialize, true); - case BIG_TREE: return new LargeOakTreeFeature(DefaultFeatureConfig::deserialize, true); - case REDWOOD: return new PineTreeFeature(DefaultFeatureConfig::deserialize); - case TALL_REDWOOD: return new SpruceTreeFeature(DefaultFeatureConfig::deserialize, true); - case BIRCH: return new BirchTreeFeature(DefaultFeatureConfig::deserialize, true, false); - case JUNGLE: return new MegaJungleTreeFeature(DefaultFeatureConfig::deserialize, true, 10, 20, JUNGLE_LOG, JUNGLE_LEAF); - case SMALL_JUNGLE: return new JungleTreeFeature(DefaultFeatureConfig::deserialize, true, 4 + random.nextInt(7), JUNGLE_LOG, JUNGLE_LEAF, false); - case SHORT_JUNGLE: return new JungleTreeFeature(DefaultFeatureConfig::deserialize, true, 4 + random.nextInt(7), JUNGLE_LOG, JUNGLE_LEAF, true); - case JUNGLE_BUSH: return new JungleGroundBushFeature(DefaultFeatureConfig::deserialize, JUNGLE_LOG, JUNGLE_SHRUB); - case SWAMP: return new SwampTreeFeature(DefaultFeatureConfig::deserialize); - case ACACIA: return new SavannaTreeFeature(DefaultFeatureConfig::deserialize, true); - case DARK_OAK: return new DarkOakTreeFeature(DefaultFeatureConfig::deserialize, true); - case MEGA_REDWOOD: return new MegaPineTreeFeature(DefaultFeatureConfig::deserialize, true, random.nextBoolean()); - case TALL_BIRCH: return new BirchTreeFeature(DefaultFeatureConfig::deserialize, true, true); - case RED_MUSHROOM: return new HugeRedMushroomFeature(PlantedFeatureConfig::deserialize); - case BROWN_MUSHROOM: return new HugeBrownMushroomFeature(PlantedFeatureConfig::deserialize); - case RANDOM: return createTreeFeatureGenerator(TreeType.values()[ThreadLocalRandom.current().nextInt(TreeType.values().length)]); - default: - return null; - } - } - - private FeatureConfig createFeatureConfig(TreeType type) { - if (type == TreeType.RED_MUSHROOM || type == TreeType.BROWN_MUSHROOM) { - return new PlantedFeatureConfig(true); - } else { - return new DefaultFeatureConfig(); + private static ResourceKey createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType type) { + return switch (type) { + // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields + case TREE -> TreePlacements.OAK_CHECKED; + case BIG_TREE -> TreePlacements.FANCY_OAK_CHECKED; + case REDWOOD -> TreePlacements.SPRUCE_CHECKED; + case TALL_REDWOOD -> TreePlacements.MEGA_SPRUCE_CHECKED; + case MEGA_REDWOOD -> TreePlacements.MEGA_PINE_CHECKED; + case BIRCH -> TreePlacements.BIRCH_CHECKED; + case JUNGLE -> TreePlacements.MEGA_JUNGLE_TREE_CHECKED; + case SMALL_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; + case SHORT_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; + case JUNGLE_BUSH -> TreePlacements.JUNGLE_BUSH; + case SWAMP -> TreePlacements.OAK_CHECKED; + case ACACIA -> TreePlacements.ACACIA_CHECKED; + case DARK_OAK -> TreePlacements.DARK_OAK_CHECKED; + case TALL_BIRCH -> TreePlacements.SUPER_BIRCH_BEES_0002; + case WARPED_FUNGUS -> TreePlacements.WARPED_FUNGI; + case CRIMSON_FUNGUS -> TreePlacements.CRIMSON_FUNGI; + case CHORUS_PLANT -> EndPlacements.CHORUS_PLANT; + case MANGROVE -> TreePlacements.MANGROVE_CHECKED; + case TALL_MANGROVE -> TreePlacements.TALL_MANGROVE_CHECKED; + case CHERRY -> TreePlacements.CHERRY_CHECKED; + case PALE_OAK -> TreePlacements.PALE_OAK_CHECKED; + case PALE_OAK_CREAKING -> TreePlacements.PALE_OAK_CREAKING_CHECKED; + case RANDOM -> createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(com.sk89q.worldedit.util.TreeGenerator.TreeType.values().length)]); + default -> null; + }; + } + + @SuppressWarnings("deprecation") + @Override + public boolean generateTree(com.sk89q.worldedit.util.TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { + ServerLevel world = (ServerLevel) getWorld(); + PlacedFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) + .map(k -> world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(k)) + .orElse(null); + ServerChunkCache chunkManager = world.getChunkSource(); + if (type == com.sk89q.worldedit.util.TreeGenerator.TreeType.CHORUS_PLANT) { + position = position.add(0, 1, 0); + } + try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { + return generator != null && generator.place( + proxyLevel.level(), chunkManager.getGenerator(), random, + FabricAdapter.toBlockPos(position) + ); } } @Override public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { - @SuppressWarnings("unchecked") - Feature generator = (Feature) createTreeFeatureGenerator(type); - return generator != null - && generator.generate(getWorld(), getWorld().getChunkManager().getChunkGenerator(), random, - FabricAdapter.toBlockPos(position), createFeatureConfig(type)); + ServerLevel world = (ServerLevel) getWorld(); + PlacedFeature generator = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); + ServerChunkCache chunkManager = world.getChunkSource(); + try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { + return generator != null && generator.place( + proxyLevel.level(), chunkManager.getGenerator(), random, + FabricAdapter.toBlockPos(position) + ); + } + } + + @Override + public boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { + ServerLevel world = (ServerLevel) getWorld(); + ConfiguredFeature feature = world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); + ServerChunkCache chunkManager = world.getChunkSource(); + try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { + return feature != null && feature.place( + proxyLevel.level(), chunkManager.getGenerator(), random, + FabricAdapter.toBlockPos(position) + ); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { + ServerLevel world = (ServerLevel) getWorld(); + Registry structureRegistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id())); + if (structure == null) { + return false; + } + + ServerChunkCache chunkManager = world.getChunkSource(); + try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { + ChunkPos chunkPos = new ChunkPos(new BlockPos(position.x(), position.y(), position.z())); + StructureStart structureStart = structure.generate( + structureRegistry.wrapAsHolder(structure), world.dimension(), world.registryAccess(), + chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), + world.getStructureManager(), world.getSeed(), chunkPos, 0, proxyLevel.level(), + biome -> true + ); + + if (!structureStart.isValid()) { + return false; + } else { + BoundingBox boundingBox = structureStart.getBoundingBox(); + ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ())); + ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ())); + ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> + structureStart.placeInChunk( + proxyLevel.level(), world.structureManager(), chunkManager.getGenerator(), world.getRandom(), + new BoundingBox(chunkPosx.getMinBlockX(), world.getMinY(), chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), world.getMaxY(), chunkPosx.getMaxBlockZ()), + chunkPosx + ) + ); + return true; + } + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } } @Override @@ -386,22 +572,27 @@ public void fixAfterFastMode(Iterable chunks) { } @Override - public void fixLighting(Iterable chunks) { - World world = getWorld(); + public void sendBiomeUpdates(Iterable chunks) { + List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); for (BlockVector2 chunk : chunks) { - world.getChunkManager().getLightingProvider().suppressLight(new ChunkPos(chunk.getBlockX(), chunk.getBlockZ()), true); + nativeChunks.add(getWorld().getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); } + ((ServerLevel) getWorld()).getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } @Override - public boolean playEffect(Vector3 position, int type, int data) { - getWorld().playLevelEvent(type, FabricAdapter.toBlockPos(position.toBlockPoint()), data); - return true; + public void fixLighting(Iterable chunks) { + Level world = getWorld(); + for (BlockVector2 chunk : chunks) { + world.getChunkSource().getLightEngine().setLightEnabled( + new ChunkPos(chunk.x(), chunk.z()), true + ); + } } @Override public WeatherType getWeather() { - LevelProperties info = getWorld().getLevelProperties(); + LevelData info = getWorld().getLevelData(); if (info.isThundering()) { return WeatherTypes.THUNDER_STORM; } @@ -413,7 +604,7 @@ public WeatherType getWeather() { @Override public long getRemainingWeatherDuration() { - LevelProperties info = getWorld().getLevelProperties(); + ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); if (info.isThundering()) { return info.getThunderTime(); } @@ -430,7 +621,7 @@ public void setWeather(WeatherType weatherType) { @Override public void setWeather(WeatherType weatherType, long duration) { - LevelProperties info = getWorld().getLevelProperties(); + ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); if (weatherType == WeatherTypes.THUNDER_STORM) { info.setClearWeatherTime(0); info.setThundering(true); @@ -446,40 +637,41 @@ public void setWeather(WeatherType weatherType, long duration) { } } + @Override + public int getMinY() { + return getWorld().getMinY(); + } + @Override public int getMaxY() { - return getWorld().getHeight() - 1; + return getWorld().getMaxY(); } @Override public BlockVector3 getSpawnPosition() { - return FabricAdapter.adapt(getWorld().getSpawnPos()); + return FabricAdapter.adapt(getWorld().getLevelData().getRespawnData().pos()); } @Override public BlockState getBlock(BlockVector3 position) { - net.minecraft.block.BlockState mcState = getWorld() - .getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4) + net.minecraft.world.level.block.state.BlockState mcState = getWorld() + .getChunk(position.x() >> 4, position.z() >> 4) .getBlockState(FabricAdapter.toBlockPos(position)); - BlockState matchingBlock = BlockStateIdAccess.getBlockStateById(Block.getRawIdFromState(mcState)); - if (matchingBlock != null) { - return matchingBlock; - } - return FabricAdapter.adapt(mcState); } @Override public BaseBlock getFullBlock(BlockVector3 position) { - BlockPos pos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + BlockPos pos = new BlockPos(position.x(), position.y(), position.z()); // Avoid creation by using the CHECK mode -- if it's needed, it'll be re-created anyways - BlockEntity tile = ((WorldChunk) getWorld().getChunk(pos)).getBlockEntity(pos, WorldChunk.CreationType.CHECK); + BlockEntity tile = ((LevelChunk) getWorld().getChunk(pos)).getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); if (tile != null) { - net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); - tile.toTag(tag); - return getBlock(position).toBaseBlock(NBTConverter.fromNative(tag)); + var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); + tile.saveWithId(tagValueOutput); + net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); + return getBlock(position).toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(tag))); } else { return getBlock(position).toBaseBlock(); } @@ -492,10 +684,9 @@ public int hashCode() { @Override public boolean equals(Object o) { - if ((o instanceof FabricWorld)) { - FabricWorld other = ((FabricWorld) o); - World otherWorld = other.worldRef.get(); - World thisWorld = worldRef.get(); + if ((o instanceof FabricWorld other)) { + Level otherWorld = other.worldRef.get(); + Level thisWorld = worldRef.get(); return otherWorld != null && otherWorld.equals(thisWorld); } else if (o instanceof com.sk89q.worldedit.world.World) { return ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); @@ -506,75 +697,87 @@ public boolean equals(Object o) { @Override public List getEntities(Region region) { - final World world = getWorld(); - if (!(world instanceof ServerWorld)) { - return Collections.emptyList(); - } - return ((ServerWorld) world).getEntities(null, entity -> true) - .stream() - .filter(e -> region.contains(FabricAdapter.adapt(e.getBlockPos()))) - .map(FabricEntity::new).collect(Collectors.toList()); + final Level world = getWorld(); + AABB box = new AABB( + FabricAdapter.toVec3(region.getMinimumPoint()), + FabricAdapter.toVec3(region.getMaximumPoint().add(BlockVector3.ONE)) + ); + List nmsEntities = world.getEntities( + (net.minecraft.world.entity.Entity) null, + box, + e -> region.contains(FabricAdapter.adapt(e.blockPosition())) + ); + return nmsEntities.stream() + .map(FabricEntity::new) + .collect(ImmutableList.toImmutableList()); } @Override public List getEntities() { - final World world = getWorld(); - if (!(world instanceof ServerWorld)) { + final Level world = getWorld(); + if (!(world instanceof ServerLevel)) { return Collections.emptyList(); } - return ((ServerWorld) world).getEntities(null, entity -> true) - .stream() - .map(FabricEntity::new) - .collect(Collectors.toList()); + return Streams.stream(((ServerLevel) world).getAllEntities()) + .map(FabricEntity::new) + .collect(ImmutableList.toImmutableList()); } @Nullable @Override public Entity createEntity(Location location, BaseEntity entity) { - World world = getWorld(); - final Optional> entityType = EntityType.get(entity.getType().getId()); - if (!entityType.isPresent()) return null; - net.minecraft.entity.Entity createdEntity = entityType.get().create(world); - if (createdEntity != null) { - CompoundTag nativeTag = entity.getNbtData(); - if (nativeTag != null) { - net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(entity.getNbtData()); - for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { - tag.remove(name); - } - createdEntity.fromTag(tag); - } - - createdEntity.setPositionAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - - world.spawnEntity(createdEntity); - return new FabricEntity(createdEntity); - } else { + ServerLevel world = (ServerLevel) getWorld(); + String entityId = entity.getType().id(); + final Optional> entityType = EntityType.byString(entityId); + if (entityType.isEmpty()) { return null; } - } + LinCompoundTag linTag = entity.getNbt(); + net.minecraft.nbt.CompoundTag tag; + if (linTag != null) { + tag = NBTConverter.toNative(linTag); + removeUnwantedEntityTagsRecursively(tag); + } else { + tag = new net.minecraft.nbt.CompoundTag(); + } + tag.putString("id", entityId); - /** - * Thrown when the reference to the world is lost. - */ - @SuppressWarnings("serial") - private static final class WorldReferenceLostException extends WorldEditException { - private WorldReferenceLostException(String message) { - super(message); + net.minecraft.world.entity.Entity createdEntity = EntityType.loadEntityRecursive(tag, world, EntitySpawnReason.COMMAND, (loadedEntity) -> { + loadedEntity.absSnapTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + return loadedEntity; + }); + if (createdEntity != null) { + world.addFreshEntityWithPassengers(createdEntity); + return new FabricEntity(createdEntity); } + return null; } - private static class NoOpChunkStatusListener implements WorldGenerationProgressListener { - @Override - public void start(ChunkPos chunkPos) { + private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); } - @Override - public void setChunkStatus(ChunkPos chunkPos, @Nullable ChunkStatus chunkStatus) { - } + // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive + tag.getList("Passengers").ifPresent(nbttaglist -> { + for (int i = 0; i < nbttaglist.size(); ++i) { + removeUnwantedEntityTagsRecursively(nbttaglist.getCompoundOrEmpty(i)); + } + }); + } - @Override - public void stop() { - } + @Override + public Mask createLiquidMask() { + return new AbstractExtentMask(this) { + @Override + public boolean test(BlockVector3 vector) { + return FabricAdapter.adapt(getExtent().getBlock(vector)).getBlock() instanceof LiquidBlock; + } + }; + } + + @Override + public boolean isValid() { + return worldRef.get() != null; } } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java index 755ee118e2..29a1021898 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java @@ -22,22 +22,34 @@ import com.mojang.brigadier.CommandDispatcher; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.PermissionCondition; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; +import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extension.platform.PlatformManager; -import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler; import com.sk89q.worldedit.internal.anvil.ChunkDeleter; +import com.sk89q.worldedit.internal.event.InteractionDebouncer; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.lifecycle.Lifecycled; +import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled; +import com.sk89q.worldedit.world.biome.BiomeCategory; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockCategory; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.gamemode.GameModes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.weather.WeatherTypes; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.AttackBlockCallback; @@ -46,30 +58,48 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.fabricmc.loader.api.metadata.version.VersionPredicate; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.tag.BlockTags; -import net.minecraft.tag.ItemTags; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.util.Identifier; -import net.minecraft.util.TypedActionResult; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.util.registry.Registry; -import net.minecraft.world.World; -import org.apache.logging.log4j.LogManager; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.phys.BlockHitResult; import org.apache.logging.log4j.Logger; +import org.enginehub.piston.Command; +import org.enginehub.worldeditcui.protocol.CUIPacket; +import org.enginehub.worldeditcui.protocol.CUIPacketHandler; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; import static com.sk89q.worldedit.fabric.FabricAdapter.adaptPlayer; @@ -80,14 +110,39 @@ */ public class FabricWorldEdit implements ModInitializer { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger LOGGER = LogManagerCompat.getLogger(); public static final String MOD_ID = "worldedit"; - public static final String CUI_PLUGIN_CHANNEL = "cui"; + + public static final Lifecycled LIFECYCLED_SERVER; + + static { + SimpleLifecycled lifecycledServer = SimpleLifecycled.invalid(); + ServerLifecycleEvents.SERVER_STARTED.register(lifecycledServer::newValue); + ServerLifecycleEvents.SERVER_STOPPING.register(__ -> lifecycledServer.invalidate()); + LIFECYCLED_SERVER = lifecycledServer; + } + + /** + * {@return current server's registry access} Not for long-term storage. + */ + public static RegistryAccess registryAccess() { + return LIFECYCLED_SERVER.valueOrThrow().registryAccess(); + } + + /** + * {@return current server's registry} Not for long-term storage. + * + * @param key the registry key + */ + public static Registry getRegistry(ResourceKey> key) { + return LIFECYCLED_SERVER.valueOrThrow().registryAccess().lookupOrThrow(key); + } private FabricPermissionsProvider provider; public static FabricWorldEdit inst; + private InteractionDebouncer debouncer; private FabricPlatform platform; private FabricConfiguration config; private Path workingDir; @@ -113,8 +168,15 @@ public void onInitialize() { throw new UncheckedIOException(e); } } + this.platform = new FabricPlatform(this); + debouncer = new InteractionDebouncer(platform); - WECUIPacketHandler.init(); + WorldEdit.getInstance().getPlatformManager().register(platform); + + config = new FabricConfiguration(this); + this.provider = getInitialPermissionsProvider(); + + CUIPacketHandler.instance().registerServerboundHandler(this::onCuiPacket); ServerTickEvents.END_SERVER_TICK.register(ThreadSafeCache.getInstance()); CommandRegistrationCallback.EVENT.register(this::registerCommands); @@ -124,83 +186,143 @@ public void onInitialize() { ServerPlayConnectionEvents.DISCONNECT.register(this::onPlayerDisconnect); AttackBlockCallback.EVENT.register(this::onLeftClickBlock); UseBlockCallback.EVENT.register(this::onRightClickBlock); - UseItemCallback.EVENT.register(this::onRightClickAir); + UseItemCallback.EVENT.register(this::onRightClickItem); LOGGER.info("WorldEdit for Fabric (version " + getInternalVersion() + ") is loaded"); } - private void registerCommands(CommandDispatcher dispatcher, boolean dedicated) { + private void registerCommands(CommandDispatcher dispatcher, CommandBuildContext registryAccess, Commands.CommandSelection environment) { + WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); - if (manager.getPlatforms().isEmpty()) { - // We'll register as part of our platform initialization later. - return; - } - - // This is a re-register (due to /reload), we must add our commands now - Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); if (commandsPlatform != platform || !platform.isHookingEvents()) { - // We're not in control of commands/events -- do not re-register. + // We're not in control of commands/events -- do not register. return; } - platform.setNativeDispatcher(dispatcher); - platform.registerCommands(manager.getPlatformCommandManager().getCommandManager()); - } - private void setupPlatform(MinecraftServer server) { - this.platform = new FabricPlatform(this, server); - - WorldEdit.getInstance().getPlatformManager().register(platform); - - this.provider = getInitialPermissionsProvider(); + List commands = manager.getPlatformCommandManager().getCommandManager() + .getAllCommands().toList(); + for (Command command : commands) { + CommandWrapper.register(dispatcher, command); + Set perms = command.getCondition().as(PermissionCondition.class) + .map(PermissionCondition::getPermissions) + .orElseGet(Collections::emptySet); + if (!perms.isEmpty()) { + perms.forEach(getPermissionsProvider()::registerPermission); + } + } } private FabricPermissionsProvider getInitialPermissionsProvider() { try { Class.forName("me.lucko.fabric.api.permissions.v0.Permissions", false, getClass().getClassLoader()); + Optional version = FabricLoader.getInstance().getModContainer("fabric-permissions-api-v0") + .map(ModContainer::getMetadata) + .map(ModMetadata::getVersion); + + if (version.isPresent() && !VersionPredicate.parse(">=0.5.0").test(version.get())) { + throw new RuntimeException("Fabric permissions version " + version.get() + " is not supported. Please update Fabric Permissions API"); + } + return new FabricPermissionsProvider.LuckoFabricPermissionsProvider(platform); } catch (ClassNotFoundException ignored) { // fallback to vanilla + } catch (Exception e) { + // catch any exception to prevent crashing the server, but still print a warning + LOGGER.warn("Failed to load Fabric permissions provider. Falling back to Minecraft", e); } + return new FabricPermissionsProvider.VanillaPermissionsProvider(platform); } private void setupRegistries(MinecraftServer server) { // Blocks - for (Identifier name : Registry.BLOCK.getIds()) { - if (BlockType.REGISTRY.get(name.toString()) == null) { - BlockType.REGISTRY.register(name.toString(), new BlockType(name.toString(), - input -> FabricAdapter.adapt(FabricAdapter.adapt(input.getBlockType()).getDefaultState()))); + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.BLOCK).keySet()) { + String key = name.toString(); + if (BlockType.REGISTRY.get(key) == null) { + BlockType.REGISTRY.register(key, new BlockType(key, + input -> FabricAdapter.adapt(FabricAdapter.adapt(input.getBlockType()).defaultBlockState()))); } } // Items - for (Identifier name : Registry.ITEM.getIds()) { - if (ItemType.REGISTRY.get(name.toString()) == null) { - ItemType.REGISTRY.register(name.toString(), new ItemType(name.toString())); + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.ITEM).keySet()) { + String key = name.toString(); + if (ItemType.REGISTRY.get(key) == null) { + ItemType.REGISTRY.register(key, new ItemType(key)); } } // Entities - for (Identifier name : Registry.ENTITY_TYPE.getIds()) { - if (EntityType.REGISTRY.get(name.toString()) == null) { - EntityType.REGISTRY.register(name.toString(), new EntityType(name.toString())); + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.ENTITY_TYPE).keySet()) { + String key = name.toString(); + if (EntityType.REGISTRY.get(key) == null) { + EntityType.REGISTRY.register(key, new EntityType(key)); } } // Biomes - for (Identifier name : server.getRegistryManager().get(Registry.BIOME_KEY).getIds()) { - if (BiomeType.REGISTRY.get(name.toString()) == null) { - BiomeType.REGISTRY.register(name.toString(), new BiomeType(name.toString())); + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { + String key = name.toString(); + if (BiomeType.REGISTRY.get(key) == null) { + BiomeType.REGISTRY.register(key, new BiomeType(key)); } } // Tags - for (Identifier name : BlockTags.getTagGroup().getTagIds()) { - if (BlockCategory.REGISTRY.get(name.toString()) == null) { - BlockCategory.REGISTRY.register(name.toString(), new BlockCategory(name.toString())); + server.registryAccess().lookupOrThrow(Registries.BLOCK).getTags().map(t -> t.key().location()).forEach(name -> { + String key = name.toString(); + if (BlockCategory.REGISTRY.get(key) == null) { + BlockCategory.REGISTRY.register(key, new BlockCategory(key)); + } + }); + server.registryAccess().lookupOrThrow(Registries.ITEM).getTags().map(t -> t.key().location()).forEach(name -> { + String key = name.toString(); + if (ItemCategory.REGISTRY.get(key) == null) { + ItemCategory.REGISTRY.register(key, new ItemCategory(key)); + } + }); + Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); + biomeRegistry.getTags().forEach(tag -> { + String key = tag.key().location().toString(); + if (BiomeCategory.REGISTRY.get(key) == null) { + BiomeCategory.REGISTRY.register(key, new BiomeCategory( + key, + () -> biomeRegistry.get(tag.key()) + .stream() + .flatMap(HolderSet.Named::stream) + .map(Holder::value) + .map(FabricAdapter::adapt) + .collect(Collectors.toSet())) + ); + } + }); + // Features + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { + String key = name.toString(); + if (ConfiguredFeatureType.REGISTRY.get(key) == null) { + ConfiguredFeatureType.REGISTRY.register(key, new ConfiguredFeatureType(key)); + } + } + // Structures + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { + String key = name.toString(); + if (StructureType.REGISTRY.get(key) == null) { + StructureType.REGISTRY.register(key, new StructureType(key)); } } - for (Identifier name : ItemTags.getTagGroup().getTagIds()) { - if (ItemCategory.REGISTRY.get(name.toString()) == null) { - ItemCategory.REGISTRY.register(name.toString(), new ItemCategory(name.toString())); + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } } } + + // ... :| + GameModes.get(""); + WeatherTypes.get(""); + com.sk89q.worldedit.registry.Registries.get(""); } private void onStartingServer(MinecraftServer minecraftServer) { @@ -211,105 +333,120 @@ private void onStartingServer(MinecraftServer minecraftServer) { } private void onStartServer(MinecraftServer minecraftServer) { - FabricAdapter.setServer(minecraftServer); - setupPlatform(minecraftServer); setupRegistries(minecraftServer); - config = new FabricConfiguration(this); config.load(); - WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); - minecraftServer.reloadResources( - minecraftServer.getDataPackManager().getEnabledNames() - ); + WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); } private void onStopServer(MinecraftServer minecraftServer) { WorldEdit worldEdit = WorldEdit.getInstance(); worldEdit.getSessionManager().unload(); - worldEdit.getPlatformManager().unregister(platform); + WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); } - private boolean shouldSkip() { - if (platform == null) { - return true; - } + private boolean skipEvents() { + return platform == null || !platform.isHookingEvents(); + } - return !platform.isHookingEvents(); // We have to be told to catch these events + private boolean skipInteractionEvent(Player player, InteractionHand hand) { + return skipEvents() || hand != InteractionHand.MAIN_HAND || player.level().isClientSide() || !(player instanceof ServerPlayer); } - private ActionResult onLeftClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockPos blockPos, Direction direction) { - if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { - return ActionResult.PASS; + private InteractionResult onLeftClickBlock(Player playerEntity, Level world, InteractionHand hand, BlockPos blockPos, Direction direction) { + if (skipInteractionEvent(playerEntity, hand)) { + return InteractionResult.PASS; } WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); + FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity); FabricWorld localWorld = getWorld(world); Location pos = new Location(localWorld, - blockPos.getX(), - blockPos.getY(), - blockPos.getZ() + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() ); com.sk89q.worldedit.util.Direction weDirection = FabricAdapter.adaptEnumFacing(direction); - if (we.handleBlockLeftClick(player, pos, weDirection)) { - return ActionResult.SUCCESS; - } + boolean result = we.handleBlockLeftClick(player, pos, weDirection) || we.handleArmSwing(player); + debouncer.setLastInteraction(player, result); - if (we.handleArmSwing(player)) { - return ActionResult.SUCCESS; - } - - return ActionResult.PASS; + return result ? InteractionResult.SUCCESS : InteractionResult.PASS; } - private ActionResult onRightClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { - if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { - return ActionResult.PASS; + private InteractionResult onRightClickBlock(Player playerEntity, Level world, InteractionHand hand, BlockHitResult blockHitResult) { + if (skipInteractionEvent(playerEntity, hand)) { + return InteractionResult.PASS; } WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); + FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity); FabricWorld localWorld = getWorld(world); Location pos = new Location(localWorld, - blockHitResult.getBlockPos().getX(), - blockHitResult.getBlockPos().getY(), - blockHitResult.getBlockPos().getZ() + blockHitResult.getBlockPos().getX(), + blockHitResult.getBlockPos().getY(), + blockHitResult.getBlockPos().getZ() ); - com.sk89q.worldedit.util.Direction direction = FabricAdapter.adaptEnumFacing(blockHitResult.getSide()); + com.sk89q.worldedit.util.Direction direction = FabricAdapter.adaptEnumFacing(blockHitResult.getDirection()); + + boolean result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player); + debouncer.setLastInteraction(player, result); + + return result ? InteractionResult.SUCCESS : InteractionResult.PASS; + } - if (we.handleBlockRightClick(player, pos, direction)) { - return ActionResult.SUCCESS; + public void onLeftClickAir(ServerPlayer playerEntity, InteractionHand hand) { + if (skipInteractionEvent(playerEntity, hand)) { + return; } - if (we.handleRightClick(player)) { - return ActionResult.SUCCESS; + WorldEdit we = WorldEdit.getInstance(); + FabricPlayer player = adaptPlayer(playerEntity); + + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + return; } - return ActionResult.PASS; + boolean result = we.handleArmSwing(player); + debouncer.setLastInteraction(player, result); } - private TypedActionResult onRightClickAir(PlayerEntity playerEntity, World world, Hand hand) { - ItemStack stackInHand = playerEntity.getStackInHand(hand); - if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { - return TypedActionResult.pass(stackInHand); + private InteractionResult onRightClickItem(Player playerEntity, Level world, InteractionHand hand) { + if (skipInteractionEvent(playerEntity, hand)) { + return InteractionResult.PASS; } WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); + FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity); - if (we.handleRightClick(player)) { - return TypedActionResult.success(stackInHand); + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + return previousResult.get() ? InteractionResult.SUCCESS : InteractionResult.PASS; } - return TypedActionResult.pass(stackInHand); + boolean result = we.handleRightClick(player); + debouncer.setLastInteraction(player, result); + + return result ? InteractionResult.SUCCESS : InteractionResult.PASS; } - // TODO Pass empty left click to server + private void onPlayerDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server) { + debouncer.clearInteraction(adaptPlayer(handler.player)); - private void onPlayerDisconnect(ServerPlayNetworkHandler handler, MinecraftServer server) { WorldEdit.getInstance().getEventBus() - .post(new SessionIdleEvent(new FabricPlayer.SessionKeyImpl(handler.player))); + .post(new SessionIdleEvent(new FabricPlayer.SessionKeyImpl(handler.player))); + } + + private void onCuiPacket(CUIPacket payload, CUIPacketHandler.PacketContext context) { + if (!(context.player() instanceof ServerPlayer player)) { + // Ignore - this is not a server-bound packet + return; + } + + FabricPlayer actor = FabricAdapter.adaptPlayer(player); + LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor); + session.handleCUIInitializationMessage(payload.eventType(), payload.args(), actor); } /** @@ -327,7 +464,7 @@ FabricConfiguration getConfig() { * @param player the player * @return the session */ - public LocalSession getSession(ServerPlayerEntity player) { + public LocalSession getSession(ServerPlayer player) { checkNotNull(player); return WorldEdit.getInstance().getSessionManager().get(adaptPlayer(player)); } @@ -338,7 +475,7 @@ public LocalSession getSession(ServerPlayerEntity player) { * @param world the world * @return the WorldEdit world */ - public FabricWorld getWorld(World world) { + public FabricWorld getWorld(Level world) { checkNotNull(world); return new FabricWorld(world); } diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java new file mode 100644 index 0000000000..632352a53d --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java @@ -0,0 +1,765 @@ +/* + * 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.neoforge; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.collect.Streams; +import com.google.common.util.concurrent.Futures; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseItem; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.AbstractExtentMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.neoforge.internal.NBTConverter; +import com.sk89q.worldedit.neoforge.internal.NeoForgeEntity; +import com.sk89q.worldedit.neoforge.internal.NeoForgeServerLevelDelegateProxy; +import com.sk89q.worldedit.neoforge.internal.NeoForgeWorldNativeAccess; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.AbstractWorld; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +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.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.item.ItemTypes; +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldedit.world.weather.WeatherTypes; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.placement.EndPlacements; +import net.minecraft.data.worldgen.placement.TreePlacements; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProblemReporter; +import net.minecraft.util.RandomSource; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.world.Clearable; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +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.LiquidBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +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.LevelData; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * An adapter to Minecraft worlds for WorldEdit. + */ +public class NeoForgeWorld extends AbstractWorld { + + private static final RandomSource random = RandomSource.create(); + + private static ResourceLocation getDimensionRegistryKey(ServerLevel world) { + return Objects.requireNonNull(world.getServer(), "server cannot be null") + .registryAccess() + .lookupOrThrow(Registries.DIMENSION_TYPE) + .getKey(world.dimensionType()); + } + + private final WeakReference worldRef; + private final NeoForgeWorldNativeAccess nativeAccess; + + /** + * Construct a new world. + * + * @param world the world + */ + NeoForgeWorld(ServerLevel world) { + checkNotNull(world); + this.worldRef = new WeakReference<>(world); + this.nativeAccess = new NeoForgeWorldNativeAccess(worldRef); + } + + /** + * Get the underlying handle to the world. + * + * @return the world + * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded) + */ + public ServerLevel getWorld() { + ServerLevel world = worldRef.get(); + if (world != null) { + return world; + } else { + throw new RuntimeException("The reference to the world was lost (i.e. the world may have been unloaded)"); + } + } + + @Override + public String getName() { + return ((ServerLevelData) getWorld().getLevelData()).getLevelName(); + } + + @Override + public String id() { + return getName() + "_" + getDimensionRegistryKey(getWorld()); + } + + @Override + public Path getStoragePath() { + final ServerLevel world = getWorld(); + return world.getServer().storageSource.getDimensionPath(world.dimension()); + } + + @Override + public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { + clearContainerBlockContents(position); + return nativeAccess.setBlock(position, block, sideEffects); + } + + @Override + public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { + nativeAccess.applySideEffects(position, previousType, sideEffectSet); + return Sets.intersection(NeoForgeWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); + } + + @Override + public int getBlockLightLevel(BlockVector3 position) { + checkNotNull(position); + return getWorld().getLightEmission(NeoForgeAdapter.toBlockPos(position)); + } + + @Override + public boolean clearContainerBlockContents(BlockVector3 position) { + checkNotNull(position); + + BlockEntity tile = getWorld().getBlockEntity(NeoForgeAdapter.toBlockPos(position)); + if (tile instanceof Clearable) { + ((Clearable) tile).clearContent(); + return true; + } + return false; + } + + @Override + public BiomeType getBiome(BlockVector3 position) { + checkNotNull(position); + + LevelChunk chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); + return getBiomeInChunk(position, chunk); + } + + private BiomeType getBiomeInChunk(BlockVector3 position, ChunkAccess chunk) { + return NeoForgeAdapter.adapt( + chunk.getNoiseBiome(position.x() >> 2, position.y() >> 2, position.z() >> 2).value() + ); + } + + @Override + public boolean setBiome(BlockVector3 position, BiomeType biome) { + checkNotNull(position); + checkNotNull(biome); + + LevelChunk chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); + var biomes = (PalettedContainer>) chunk.getSection(chunk.getSectionIndex(position.y())).getBiomes(); + biomes.getAndSetUnchecked( + position.x() & 3, position.y() & 3, position.z() & 3, + getWorld().registryAccess().lookupOrThrow(Registries.BIOME) + .getOrThrow(ResourceKey.create(Registries.BIOME, ResourceLocation.parse(biome.id()))) + ); + chunk.markUnsaved(); + return true; + } + + private static final LoadingCache fakePlayers + = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(NeoForgeFakePlayer::new)); + + @Override + public boolean useItem(BlockVector3 position, BaseItem item, Direction face) { + ItemStack stack = NeoForgeAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtReference(), 1)); + ServerLevel world = getWorld(); + final NeoForgeFakePlayer fakePlayer; + try { + fakePlayer = fakePlayers.get(world); + } catch (ExecutionException ignored) { + return false; + } + fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); + fakePlayer.absSnapTo(position.x(), position.y(), position.z(), + (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); + final BlockPos blockPos = NeoForgeAdapter.toBlockPos(position); + final BlockHitResult rayTraceResult = new BlockHitResult(NeoForgeAdapter.toVec3(position), + NeoForgeAdapter.adapt(face), blockPos, false); + UseOnContext itemUseContext = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); + InteractionResult used = stack.onItemUseFirst(itemUseContext); + if (used != InteractionResult.SUCCESS) { + // try activating the block + InteractionResult resultType = getWorld().getBlockState(blockPos).useItemOn(stack, world, fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); + if (resultType.consumesAction()) { + used = resultType; + } else { + used = stack.getItem().use(world, fakePlayer, InteractionHand.MAIN_HAND); + } + } + return used == InteractionResult.SUCCESS; + } + + @Override + public void dropItem(Vector3 position, BaseItemStack item) { + checkNotNull(position); + checkNotNull(item); + + if (item.getType() == ItemTypes.AIR) { + return; + } + + ItemEntity entity = new ItemEntity(getWorld(), position.x(), position.y(), position.z(), NeoForgeAdapter.adapt(item)); + entity.setPickUpDelay(10); + getWorld().addFreshEntity(entity); + } + + @Override + public void simulateBlockMine(BlockVector3 position) { + BlockPos pos = NeoForgeAdapter.toBlockPos(position); + getWorld().destroyBlock(pos, true); + } + + @Override + public boolean canPlaceAt(BlockVector3 position, BlockState blockState) { + return NeoForgeAdapter.adapt(blockState).canSurvive(getWorld(), NeoForgeAdapter.toBlockPos(position)); + } + + // For unmapped regen names, see Fabric! + + @Override + public boolean regenerate(Region region, Extent extent, RegenOptions options) { + try { + doRegen(region, extent, options); + } catch (Exception e) { + throw new IllegalStateException("Regen failed", e); + } + + return true; + } + + private void doRegen(Region region, Extent extent, RegenOptions options) throws Exception { + Path tempDir = Files.createTempDirectory("WorldEditWorldGen"); + LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); + try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("WorldEditTempGen")) { + ServerLevel originalWorld = getWorld(); + PrimaryLevelData levelProperties = (PrimaryLevelData) originalWorld.getServer() + .getWorldData().overworldData(); + WorldOptions originalOpts = levelProperties.worldGenOptions(); + + long seed = options.getSeed().orElse(originalWorld.getSeed()); + + levelProperties.worldOptions = options.getSeed().isPresent() + ? originalOpts.withSeed(OptionalLong.of(seed)) + : originalOpts; + + ResourceKey worldRegKey = originalWorld.dimension(); + try (ServerLevel serverWorld = new ServerLevel( + originalWorld.getServer(), Util.backgroundExecutor(), session, + ((ServerLevelData) originalWorld.getLevelData()), + worldRegKey, + new LevelStem( + originalWorld.dimensionTypeRegistration(), + originalWorld.getChunkSource().getGenerator() + ), + originalWorld.isDebug(), + seed, + // No spawners are needed for this world. + ImmutableList.of(), + // This controls ticking, we don't need it so set it to false. + false, + originalWorld.getRandomSequences() + )) { + regenForWorld(region, extent, serverWorld, options); + + // drive the server executor until all tasks are popped off + while (originalWorld.getServer().pollTask()) { + Thread.yield(); + } + } finally { + levelProperties.worldOptions = originalOpts; + } + } finally { + SafeFiles.tryHardToDeleteDir(tempDir); + } + } + + private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, + RegenOptions options) throws WorldEditException { + List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); + + // drive executor until loading finishes + BlockableEventLoop executor = serverWorld.getChunkSource().mainThreadProcessor; + executor.managedBlock(() -> { + // bail out early if a future fails + if (chunkLoadings.stream().anyMatch(ftr -> + ftr.isDone() && Futures.getUnchecked(ftr) == null + )) { + return false; + } + return chunkLoadings.stream().allMatch(CompletableFuture::isDone); + }); + + Map chunks = new HashMap<>(); + for (CompletableFuture future : chunkLoadings) { + @Nullable + ChunkAccess chunk = future.getNow(null); + checkState(chunk != null, "Failed to generate a chunk, regen failed."); + chunks.put(chunk.getPos(), chunk); + } + + for (BlockVector3 vec : region) { + BlockPos pos = NeoForgeAdapter.toBlockPos(vec); + ChunkAccess chunk = chunks.get(new ChunkPos(pos)); + BlockStateHolder state = NeoForgeAdapter.adapt(chunk.getBlockState(pos)); + BlockEntity blockEntity = chunk.getBlockEntity(pos); + if (blockEntity != null) { + var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); + blockEntity.saveWithId(tagValueOutput); + net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); + state = state.toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(tag))); + } + extent.setBlock(vec, state.toBaseBlock()); + + if (options.shouldRegenBiomes()) { + BiomeType biome = getBiomeInChunk(vec, chunk); + extent.setBiome(vec, biome); + } + } + } + + private List> submitChunkLoadTasks(Region region, ServerLevel world) { + List> chunkLoadings = new ArrayList<>(); + // Pre-gen all the chunks + for (BlockVector2 chunk : region.getChunks()) { + chunkLoadings.add( + world.getChunkSource().getChunkFuture(chunk.x(), chunk.z(), ChunkStatus.FEATURES, true) + .thenApply(either -> either.orElse(null)) + ); + } + return chunkLoadings; + } + + @SuppressWarnings("deprecation") + @Nullable + private static ResourceKey createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType type) { + return switch (type) { + // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields + case TREE -> TreePlacements.OAK_CHECKED; + case BIG_TREE -> TreePlacements.FANCY_OAK_CHECKED; + case REDWOOD -> TreePlacements.SPRUCE_CHECKED; + case TALL_REDWOOD -> TreePlacements.MEGA_SPRUCE_CHECKED; + case MEGA_REDWOOD -> TreePlacements.MEGA_PINE_CHECKED; + case BIRCH -> TreePlacements.BIRCH_CHECKED; + case JUNGLE -> TreePlacements.MEGA_JUNGLE_TREE_CHECKED; + case SMALL_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; + case SHORT_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; + case JUNGLE_BUSH -> TreePlacements.JUNGLE_BUSH; + case SWAMP -> TreePlacements.OAK_CHECKED; + case ACACIA -> TreePlacements.ACACIA_CHECKED; + case DARK_OAK -> TreePlacements.DARK_OAK_CHECKED; + case TALL_BIRCH -> TreePlacements.SUPER_BIRCH_BEES_0002; + case WARPED_FUNGUS -> TreePlacements.WARPED_FUNGI; + case CRIMSON_FUNGUS -> TreePlacements.CRIMSON_FUNGI; + case CHORUS_PLANT -> EndPlacements.CHORUS_PLANT; + case MANGROVE -> TreePlacements.MANGROVE_CHECKED; + case TALL_MANGROVE -> TreePlacements.TALL_MANGROVE_CHECKED; + case CHERRY -> TreePlacements.CHERRY_CHECKED; + case PALE_OAK -> TreePlacements.PALE_OAK_CHECKED; + case PALE_OAK_CREAKING -> TreePlacements.PALE_OAK_CREAKING_CHECKED; + case RANDOM -> createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(com.sk89q.worldedit.util.TreeGenerator.TreeType.values().length)]); + default -> null; + }; + } + + @SuppressWarnings("deprecation") + @Override + public boolean generateTree(com.sk89q.worldedit.util.TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { + ServerLevel world = getWorld(); + PlacedFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) + .map(k -> world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(k)) + .orElse(null); + ServerChunkCache chunkManager = world.getChunkSource(); + if (type == com.sk89q.worldedit.util.TreeGenerator.TreeType.CHORUS_PLANT) { + position = position.add(0, 1, 0); + } + try (NeoForgeServerLevelDelegateProxy.LevelAndProxy proxyLevel = NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { + return generator != null && generator.place( + proxyLevel.level(), chunkManager.getGenerator(), random, + NeoForgeAdapter.toBlockPos(position) + ); + } + } + + @Override + public boolean generateTree(com.sk89q.worldedit.world.generation.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { + ServerLevel world = getWorld(); + PlacedFeature generator = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); + ServerChunkCache chunkManager = world.getChunkSource(); + try (NeoForgeServerLevelDelegateProxy.LevelAndProxy proxyLevel = NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { + return generator != null && generator.place( + proxyLevel.level(), chunkManager.getGenerator(), random, + NeoForgeAdapter.toBlockPos(position) + ); + } + } + + @Override + public boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { + ServerLevel world = getWorld(); + ConfiguredFeature feature = world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); + ServerChunkCache chunkManager = world.getChunkSource(); + try (NeoForgeServerLevelDelegateProxy.LevelAndProxy levelProxy = + NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { + return feature != null && feature.place(levelProxy.level(), chunkManager.getGenerator(), random, NeoForgeAdapter.toBlockPos(position)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { + ServerLevel world = getWorld(); + Registry structureRegistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id())); + if (structure == null) { + return false; + } + + ServerChunkCache chunkManager = world.getChunkSource(); + try (NeoForgeServerLevelDelegateProxy.LevelAndProxy levelProxy = + NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { + ChunkPos chunkPos = new ChunkPos(new BlockPos(position.x(), position.y(), position.z())); + StructureStart structureStart = structure.generate( + structureRegistry.wrapAsHolder(structure), world.dimension(), world.registryAccess(), + chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), + world.getStructureManager(), world.getSeed(), chunkPos, 0, levelProxy.level(), + biome -> true + ); + + if (!structureStart.isValid()) { + return false; + } else { + BoundingBox boundingBox = structureStart.getBoundingBox(); + ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ())); + ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ())); + ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> + structureStart.placeInChunk( + levelProxy.level(), world.structureManager(), chunkManager.getGenerator(), world.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), world.getMinY(), chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), world.getMaxY(), chunkPosx.getMaxBlockZ() + ), + chunkPosx + ) + ); + return true; + } + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + @Override + public void checkLoadedChunk(BlockVector3 pt) { + getWorld().getChunk(NeoForgeAdapter.toBlockPos(pt)); + } + + @Override + public void fixAfterFastMode(Iterable chunks) { + fixLighting(chunks); + } + + @Override + public void sendBiomeUpdates(Iterable chunks) { + List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); + for (BlockVector2 chunk : chunks) { + nativeChunks.add(getWorld().getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); + } + getWorld().getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); + } + + @Override + public void fixLighting(Iterable chunks) { + ServerLevel world = getWorld(); + for (BlockVector2 chunk : chunks) { + // Fetch the chunk after light initialization at least + // We'll be doing a full relight anyways, so we don't need to be LIGHT yet + world.getChunkSource().getLightEngine().lightChunk(world.getChunk( + chunk.x(), chunk.z(), ChunkStatus.INITIALIZE_LIGHT + ), false); + } + } + + @Override + public WeatherType getWeather() { + LevelData info = getWorld().getLevelData(); + if (info.isThundering()) { + return WeatherTypes.THUNDER_STORM; + } + if (info.isRaining()) { + return WeatherTypes.RAIN; + } + return WeatherTypes.CLEAR; + } + + @Override + public long getRemainingWeatherDuration() { + ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); + if (info.isThundering()) { + return info.getThunderTime(); + } + if (info.isRaining()) { + return info.getRainTime(); + } + return info.getClearWeatherTime(); + } + + @Override + public void setWeather(WeatherType weatherType) { + setWeather(weatherType, 0); + } + + @Override + public void setWeather(WeatherType weatherType, long duration) { + ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); + if (weatherType == WeatherTypes.THUNDER_STORM) { + info.setClearWeatherTime(0); + info.setThundering(true); + info.setThunderTime((int) duration); + } else if (weatherType == WeatherTypes.RAIN) { + info.setClearWeatherTime(0); + info.setRaining(true); + info.setRainTime((int) duration); + } else if (weatherType == WeatherTypes.CLEAR) { + info.setRaining(false); + info.setThundering(false); + info.setClearWeatherTime((int) duration); + } + } + + @Override + public int getMinY() { + return getWorld().getMinY(); + } + + @Override + public int getMaxY() { + return getWorld().getMaxY(); + } + + @Override + public BlockVector3 getSpawnPosition() { + return NeoForgeAdapter.adapt(getWorld().getLevelData().getRespawnData().pos()); + } + + @Override + public BlockState getBlock(BlockVector3 position) { + net.minecraft.world.level.block.state.BlockState mcState = getWorld() + .getChunk(position.x() >> 4, position.z() >> 4) + .getBlockState(NeoForgeAdapter.toBlockPos(position)); + + return NeoForgeAdapter.adapt(mcState); + } + + @Override + public BaseBlock getFullBlock(BlockVector3 position) { + BlockPos pos = new BlockPos(position.x(), position.y(), position.z()); + BlockEntity tile = getWorld().getChunk(pos).getBlockEntity(pos); + + if (tile != null) { + var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); + tile.saveWithId(tagValueOutput); + net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); + return getBlock(position).toBaseBlock( + LazyReference.from(() -> NBTConverter.fromNative(tag)) + ); + } else { + return getBlock(position).toBaseBlock(); + } + } + + @Override + public int hashCode() { + return getWorld().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } else if ((o instanceof NeoForgeWorld other)) { + Level otherWorld = other.worldRef.get(); + Level thisWorld = worldRef.get(); + return otherWorld != null && otherWorld.equals(thisWorld); + } else if (o instanceof com.sk89q.worldedit.world.World) { + return ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); + } else { + return false; + } + } + + @Override + public List getEntities(Region region) { + final ServerLevel world = getWorld(); + AABB box = new AABB( + NeoForgeAdapter.toVec3(region.getMinimumPoint()), + NeoForgeAdapter.toVec3(region.getMaximumPoint().add(BlockVector3.ONE)) + ); + List nmsEntities = world.getEntities( + (net.minecraft.world.entity.Entity) null, + box, + e -> region.contains(NeoForgeAdapter.adapt(e.blockPosition())) + ); + return nmsEntities.stream().map(NeoForgeEntity::new).collect(ImmutableList.toImmutableList()); + } + + @Override + public List getEntities() { + final ServerLevel world = getWorld(); + return Streams.stream(world.getAllEntities()) + .map(NeoForgeEntity::new) + .collect(ImmutableList.toImmutableList()); + } + + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity entity) { + ServerLevel world = getWorld(); + String entityId = entity.getType().id(); + final Optional> entityType = EntityType.byString(entityId); + if (entityType.isEmpty()) { + return null; + } + LinCompoundTag nativeTag = entity.getNbt(); + net.minecraft.nbt.CompoundTag tag; + if (nativeTag != null) { + tag = NBTConverter.toNative(nativeTag); + removeUnwantedEntityTagsRecursively(tag); + } else { + tag = new net.minecraft.nbt.CompoundTag(); + } + tag.putString("id", entityId); + + net.minecraft.world.entity.Entity createdEntity = EntityType.loadEntityRecursive(tag, world, EntitySpawnReason.COMMAND, (loadedEntity) -> { + loadedEntity.absSnapTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + return loadedEntity; + }); + if (createdEntity != null) { + world.addFreshEntityWithPassengers(createdEntity); + return new NeoForgeEntity(createdEntity); + } + return null; + } + + private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + + // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive + tag.getList("Passengers").ifPresent(nbttaglist -> { + for (int i = 0; i < nbttaglist.size(); ++i) { + removeUnwantedEntityTagsRecursively(nbttaglist.getCompoundOrEmpty(i)); + } + }); + } + + @Override + public Mask createLiquidMask() { + return new AbstractExtentMask(this) { + @Override + public boolean test(BlockVector3 vector) { + return NeoForgeAdapter.adapt(getExtent().getBlock(vector)).getBlock() instanceof LiquidBlock; + } + }; + } + + @Override + public boolean isValid() { + return worldRef.get() != null; + } +} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java new file mode 100644 index 0000000000..fcb84a283a --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java @@ -0,0 +1,501 @@ +/* + * 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.neoforge; + +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.PermissionCondition; +import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; +import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.internal.anvil.ChunkDeleter; +import com.sk89q.worldedit.internal.event.InteractionDebouncer; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.biome.BiomeCategory; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.gamemode.GameModes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; +import com.sk89q.worldedit.world.item.ItemCategory; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.weather.WeatherTypes; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.CommandEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import org.apache.logging.log4j.Logger; +import org.enginehub.piston.Command; +import org.enginehub.worldeditcui.protocol.CUIPacket; +import org.enginehub.worldeditcui.protocol.CUIPacketHandler; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; +import static com.sk89q.worldedit.neoforge.NeoForgeAdapter.adaptCommandSource; +import static com.sk89q.worldedit.neoforge.NeoForgeAdapter.adaptPlayer; + +/** + * The Forge implementation of WorldEdit. + */ +@Mod(NeoForgeWorldEdit.MOD_ID) +public class NeoForgeWorldEdit { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + public static final String MOD_ID = "worldedit"; + + private NeoForgePermissionsProvider provider; + + public static NeoForgeWorldEdit inst; + + private InteractionDebouncer debouncer; + private NeoForgePlatform platform; + private NeoForgeConfiguration config; + private Path workingDir; + + private ModContainer container; + + public NeoForgeWorldEdit(IEventBus modBus) { + inst = this; + + modBus.addListener(this::init); + + NeoForge.EVENT_BUS.register(ThreadSafeCache.getInstance()); + NeoForge.EVENT_BUS.register(this); + } + + private void init(FMLCommonSetupEvent event) { + this.container = ModLoadingContext.get().getActiveContainer(); + + // Setup working directory + workingDir = FMLPaths.CONFIGDIR.get().resolve("worldedit"); + if (!Files.exists(workingDir)) { + try { + Files.createDirectory(workingDir); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + CUIPacketHandler.instance().registerServerboundHandler(this::onCuiPacket); + + setupPlatform(); + + LOGGER.info("WorldEdit for NeoForge (version {}) is loaded", getInternalVersion()); + } + + private void setupPlatform() { + this.platform = new NeoForgePlatform(this); + debouncer = new InteractionDebouncer(platform); + + WorldEdit.getInstance().getPlatformManager().register(platform); + + config = new NeoForgeConfiguration(this); + + this.provider = new NeoForgePermissionsProvider.VanillaPermissionsProvider(platform); + } + + private void setupRegistries(MinecraftServer server) { + // Blocks + for (ResourceLocation name : BuiltInRegistries.BLOCK.keySet()) { + String key = name.toString(); + if (BlockType.REGISTRY.get(key) == null) { + BlockType.REGISTRY.register(key, new BlockType(key, + input -> NeoForgeAdapter.adapt(NeoForgeAdapter.adapt(input.getBlockType()).defaultBlockState()))); + } + } + // Items + for (ResourceLocation name : BuiltInRegistries.ITEM.keySet()) { + String key = name.toString(); + if (ItemType.REGISTRY.get(key) == null) { + ItemType.REGISTRY.register(key, new ItemType(key)); + } + } + // Entities + for (ResourceLocation name : BuiltInRegistries.ENTITY_TYPE.keySet()) { + String key = name.toString(); + if (EntityType.REGISTRY.get(key) == null) { + EntityType.REGISTRY.register(key, new EntityType(key)); + } + } + // Biomes + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { + String key = name.toString(); + if (BiomeType.REGISTRY.get(key) == null) { + BiomeType.REGISTRY.register(key, new BiomeType(key)); + } + } + // Tags + server.registryAccess().lookupOrThrow(Registries.BLOCK).getTags().map(t -> t.key().location()).forEach(name -> { + String key = name.toString(); + if (BlockCategory.REGISTRY.get(key) == null) { + BlockCategory.REGISTRY.register(key, new BlockCategory(key)); + } + }); + server.registryAccess().lookupOrThrow(Registries.ITEM).getTags().map(t -> t.key().location()).forEach(name -> { + String key = name.toString(); + if (ItemCategory.REGISTRY.get(key) == null) { + ItemCategory.REGISTRY.register(key, new ItemCategory(key)); + } + }); + Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); + biomeRegistry.getTags().forEach(tag -> { + String key = tag.key().location().toString(); + if (BiomeCategory.REGISTRY.get(key) == null) { + BiomeCategory.REGISTRY.register(key, new BiomeCategory( + key, + () -> biomeRegistry.get(tag.key()) + .stream() + .flatMap(HolderSet.Named::stream) + .map(Holder::value) + .map(NeoForgeAdapter::adapt) + .collect(Collectors.toSet())) + ); + } + }); + // Features + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { + String key = name.toString(); + if (ConfiguredFeatureType.REGISTRY.get(key) == null) { + ConfiguredFeatureType.REGISTRY.register(key, new ConfiguredFeatureType(key)); + } + } + // Structures + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { + String key = name.toString(); + if (StructureType.REGISTRY.get(key) == null) { + StructureType.REGISTRY.register(key, new StructureType(key)); + } + } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + + // ... :| + GameModes.get(""); + WeatherTypes.get(""); + com.sk89q.worldedit.registry.Registries.get(""); + } + + @SubscribeEvent + public void registerCommands(RegisterCommandsEvent event) { + WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); + + PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); + Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); + if (commandsPlatform != platform || !platform.isHookingEvents()) { + // We're not in control of commands/events -- do not register. + return; + } + + List commands = manager.getPlatformCommandManager().getCommandManager() + .getAllCommands().toList(); + for (Command command : commands) { + CommandWrapper.register(event.getDispatcher(), command); + Set perms = command.getCondition().as(PermissionCondition.class) + .map(PermissionCondition::getPermissions) + .orElseGet(Collections::emptySet); + if (!perms.isEmpty()) { + perms.forEach(getPermissionsProvider()::registerPermission); + } + } + } + + @SubscribeEvent + public void serverAboutToStart(ServerAboutToStartEvent event) { + final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME); + if (Files.exists(delChunks)) { + ChunkDeleter.runFromFile(delChunks, true); + } + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) { + WorldEdit worldEdit = WorldEdit.getInstance(); + worldEdit.getSessionManager().unload(); + WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); + } + + @SubscribeEvent + public void serverStarted(ServerStartedEvent event) { + setupRegistries(event.getServer()); + + config.load(); + WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); + } + + private boolean skipEvents() { + return platform == null || !platform.isHookingEvents(); + } + + private boolean skipInteractionEvent(Player player, InteractionHand hand) { + return skipEvents() || hand != InteractionHand.MAIN_HAND || player.level().isClientSide() || !(player instanceof ServerPlayer); + } + + @SubscribeEvent + public void onLeftClickBlock(PlayerInteractEvent.LeftClickBlock event) { + if (skipInteractionEvent(event.getEntity(), event.getHand()) || event.getUseItem().isFalse()) { + return; + } + + ServerPlayer playerEntity = (ServerPlayer) event.getEntity(); + WorldEdit we = WorldEdit.getInstance(); + NeoForgePlayer player = adaptPlayer(playerEntity); + NeoForgeWorld world = getWorld(playerEntity.level()); + Direction direction = NeoForgeAdapter.adaptEnumFacing(event.getFace()); + + BlockPos blockPos = event.getPos(); + Location pos = new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()); + + boolean result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player); + debouncer.setLastInteraction(player, result); + + if (result) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) { + if (skipInteractionEvent(event.getEntity(), event.getHand()) || event.getUseItem().isFalse()) { + return; + } + + ServerPlayer playerEntity = (ServerPlayer) event.getEntity(); + WorldEdit we = WorldEdit.getInstance(); + NeoForgePlayer player = adaptPlayer(playerEntity); + NeoForgeWorld world = getWorld(playerEntity.level()); + Direction direction = NeoForgeAdapter.adaptEnumFacing(event.getFace()); + + BlockPos blockPos = event.getPos(); + Location pos = new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()); + + boolean result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player); + debouncer.setLastInteraction(player, result); + + if (result) { + event.setCanceled(true); + } + } + + public void onLeftClickAir(ServerPlayer playerEntity, InteractionHand hand) { + if (skipInteractionEvent(playerEntity, hand)) { + return; + } + + WorldEdit we = WorldEdit.getInstance(); + NeoForgePlayer player = adaptPlayer(playerEntity); + + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + return; + } + + boolean result = we.handleArmSwing(player); + debouncer.setLastInteraction(player, result); + } + + @SubscribeEvent + public void onRightClickItem(PlayerInteractEvent.RightClickItem event) { + if (skipInteractionEvent(event.getEntity(), event.getHand())) { + return; + } + + ServerPlayer playerEntity = (ServerPlayer) event.getEntity(); + WorldEdit we = WorldEdit.getInstance(); + NeoForgePlayer player = adaptPlayer(playerEntity); + + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + if (previousResult.get()) { + event.setCanceled(true); + } + return; + } + + boolean result = we.handleRightClick(player); + debouncer.setLastInteraction(player, result); + + if (result) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public void onCommandEvent(CommandEvent event) throws CommandSyntaxException { + ParseResults parseResults = event.getParseResults(); + if (parseResults.getContext().getSource().getEntity() instanceof ServerPlayer player && player.level().isClientSide()) { + return; + } + if (parseResults.getContext().getCommand() != CommandWrapper.FAKE_COMMAND) { + return; + } + event.setCanceled(true); + WorldEdit.getInstance().getEventBus().post(new com.sk89q.worldedit.event.platform.CommandEvent( + adaptCommandSource(parseResults.getContext().getSource()), + "/" + parseResults.getReader().getString() + )); + } + + @SubscribeEvent + public void onPlayerLogOut(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.getEntity() instanceof ServerPlayer player) { + debouncer.clearInteraction(adaptPlayer(player)); + + WorldEdit.getInstance().getEventBus() + .post(new SessionIdleEvent(new NeoForgePlayer.SessionKeyImpl(player))); + } + } + + private void onCuiPacket(CUIPacket payload, CUIPacketHandler.PacketContext context) { + if (!(context.player() instanceof ServerPlayer player)) { + // Ignore - this is not a server-bound packet + return; + } + NeoForgePlayer actor = NeoForgeAdapter.adaptPlayer(player); + LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor); + session.handleCUIInitializationMessage(payload.eventType(), payload.args(), actor); + } + + /** + * Get the configuration. + * + * @return the Forge configuration + */ + NeoForgeConfiguration getConfig() { + return this.config; + } + + /** + * Get the session for a player. + * + * @param player the player + * @return the session + */ + public LocalSession getSession(ServerPlayer player) { + checkNotNull(player); + return WorldEdit.getInstance().getSessionManager().get(adaptPlayer(player)); + } + + /** + * Get the WorldEdit proxy for the given world. + * + * @param world the world + * @return the WorldEdit world + */ + public NeoForgeWorld getWorld(ServerLevel world) { + checkNotNull(world); + return new NeoForgeWorld(world); + } + + /** + * Get the WorldEdit proxy for the platform. + * + * @return the WorldEdit platform + */ + public Platform getPlatform() { + return this.platform; + } + + /** + * Get the working directory where WorldEdit's files are stored. + * + * @return the working directory + */ + public Path getWorkingDir() { + return this.workingDir; + } + + /** + * Get the version of the WorldEdit-for-Forge implementation. + * + * @return a version string + */ + String getInternalVersion() { + return container.getModInfo().getVersion().toString(); + } + + public void setPermissionsProvider(NeoForgePermissionsProvider provider) { + this.provider = provider; + } + + public NeoForgePermissionsProvider getPermissionsProvider() { + return provider; + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java index db945669af..fd654fc078 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java @@ -19,93 +19,124 @@ package com.sk89q.worldedit.sponge; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.flowpowered.math.vector.Vector3d; -import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.sponge.internal.NbtAdapter; +import com.sk89q.worldedit.sponge.internal.SpongeWorldNativeAccess; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.world.AbstractWorld; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeType; 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.placement.EndPlacements; +import net.minecraft.data.worldgen.placement.TreePlacements; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import org.apache.logging.log4j.Logger; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinIntTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; -import org.spongepowered.api.block.BlockSnapshot; -import org.spongepowered.api.block.BlockState; -import org.spongepowered.api.block.BlockType; -import org.spongepowered.api.block.BlockTypes; -import org.spongepowered.api.block.tileentity.TileEntity; -import org.spongepowered.api.data.key.Keys; -import org.spongepowered.api.data.property.block.GroundLuminanceProperty; -import org.spongepowered.api.data.property.block.SkyLuminanceProperty; +import org.spongepowered.api.block.entity.BlockEntity; +import org.spongepowered.api.block.entity.BlockEntityArchetype; +import org.spongepowered.api.block.entity.BlockEntityType; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.entity.EntityArchetype; import org.spongepowered.api.entity.EntityType; import org.spongepowered.api.entity.EntityTypes; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.util.Ticks; import org.spongepowered.api.world.BlockChangeFlags; -import org.spongepowered.api.world.World; -import org.spongepowered.api.world.weather.Weather; +import org.spongepowered.api.world.LightTypes; +import org.spongepowered.api.world.SerializationBehavior; +import org.spongepowered.api.world.generation.config.WorldGenerationConfig; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.api.world.server.WorldArchetype; +import org.spongepowered.api.world.server.WorldArchetypeType; +import org.spongepowered.api.world.server.storage.ServerWorldProperties; +import org.spongepowered.api.world.volume.stream.StreamOptions; +import org.spongepowered.math.vector.Vector3d; +import org.spongepowered.math.vector.Vector3i; import java.lang.ref.WeakReference; import java.nio.file.Path; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.Optional; - +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; + /** * An adapter to Minecraft worlds for WorldEdit. */ -public abstract class SpongeWorld extends AbstractWorld { +public final class SpongeWorld extends AbstractWorld { + + private static final RandomSource random = RandomSource.create(); + private static final Logger LOGGER = LogManagerCompat.getLogger(); - private final WeakReference worldRef; + private final WeakReference worldRef; + private final SpongeWorldNativeAccess worldNativeAccess; /** * Construct a new world. * * @param world the world */ - protected SpongeWorld(World world) { + SpongeWorld(ServerWorld world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); + this.worldNativeAccess = new SpongeWorldNativeAccess(new WeakReference<>((ServerLevel) world)); } /** * Get the underlying handle to the world. * * @return the world - * @throws WorldEditException thrown if a reference to the world was lost (i.e. world was unloaded) - */ - public World getWorldChecked() throws WorldEditException { - World world = worldRef.get(); - if (world != null) { - return world; - } else { - throw new WorldReferenceLostException("The reference to the world was lost (i.e. the world may have been unloaded)"); - } - } - - /** - * Get the underlying handle to the world. - * - * @return the world - * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded) + * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was + * unloaded) */ - public World getWorld() { - World world = worldRef.get(); + ServerWorld getWorld() { + ServerWorld world = worldRef.get(); if (world != null) { return world; } else { @@ -113,109 +144,272 @@ public World getWorld() { } } + // This is sus but leaving it for later world name/id reworks @Override public String getName() { - return getWorld().getName(); + return getWorld().key().asString(); } @Override - public String getId() { - return getName().replace(" ", "_").toLowerCase(Locale.ROOT) + - getWorld().getDimension().getType().getName().toLowerCase(Locale.ROOT); + public String id() { + return getWorld().key().asString(); } @Override public Path getStoragePath() { - return getWorld().getDirectory(); + return getWorld().directory(); } - @SuppressWarnings("WeakerAccess") - protected BlockState getBlockState(BlockStateHolder block) { - if (block instanceof com.sk89q.worldedit.world.block.BlockState) { - BlockState state = Sponge.getRegistry().getType(BlockType.class, block.getBlockType().getId()).orElse(BlockTypes.AIR).getDefaultState(); - for (Map.Entry, Object> entry : block.getStates().entrySet()) { - // TODO Convert across states - } - return state; - } else { - throw new UnsupportedOperationException("Missing Sponge adapter for WorldEdit!"); + @Override + public BlockState getBlock(BlockVector3 position) { + return SpongeAdapter.adapt(getWorld().block( + position.x(), position.y(), position.z() + )); + } + + @Override + public BaseBlock getFullBlock(BlockVector3 position) { + BlockEntity blockEntity = getWorld().blockEntity( + position.x(), position.y(), position.z() + ).orElse(null); + LinCompoundTag blockEntityData = null; + if (blockEntity != null) { + BlockEntityArchetype blockEntityArchetype = blockEntity.createArchetype(); + BlockEntityType blockEntityType = blockEntityArchetype.blockEntityType(); + ResourceKey blockEntityId = blockEntityType.key(RegistryTypes.BLOCK_ENTITY_TYPE); + blockEntityData = NbtAdapter.adaptToWorldEdit(blockEntityArchetype.blockEntityData()); + + // Add ID and position since Sponge's #blockEntityData does not save metadata + LinCompoundTag.Builder fullBlockEntityDataBuilder = blockEntityData.toBuilder(); + fullBlockEntityDataBuilder.put("id", LinStringTag.of(blockEntityId.formatted())); + fullBlockEntityDataBuilder.put("x", LinIntTag.of(position.x())); + fullBlockEntityDataBuilder.put("y", LinIntTag.of(position.y())); + fullBlockEntityDataBuilder.put("z", LinIntTag.of(position.z())); + blockEntityData = fullBlockEntityDataBuilder.build(); } + return getBlock(position).toBaseBlock(blockEntityData); } - @SuppressWarnings("WeakerAccess") - protected abstract void applyTileEntityData(TileEntity entity, BaseBlock block); + @Override + public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { + checkNotNull(position); + checkNotNull(block); + + ServerWorld world = getWorld(); + + org.spongepowered.api.block.BlockState newState = SpongeAdapter.adapt(block.toImmutableState()); + + boolean didSet = world.setBlock( + position.x(), position.y(), position.z(), + newState, + BlockChangeFlags.NONE + .withUpdateNeighbors(sideEffects.shouldApply(SideEffect.NEIGHBORS)) + .withNotifyClients(true) + .withPhysics(sideEffects.shouldApply(SideEffect.UPDATE)) + .withNotifyObservers(sideEffects.shouldApply(SideEffect.UPDATE)) + .withLightingUpdates(sideEffects.shouldApply(SideEffect.LIGHTING)) + .withPathfindingUpdates(sideEffects.shouldApply(SideEffect.ENTITY_AI)) + .withNeighborDropsAllowed(false) + .withBlocksMoving(false) + .withForcedReRender(false) + .withIgnoreRender(false) + .withPerformBlockDestruction(false) + ); + if (!didSet) { + // still update NBT if the block is the same + if (world.block(position.x(), position.y(), position.z()) == newState) { + didSet = block.toBaseBlock().getNbt() != null; + } + } + + // Create the TileEntity + if (didSet && block instanceof BaseBlock baseBlock) { + LinCompoundTag nbt = baseBlock.getNbt(); + if (nbt != null) { + BlockEntityArchetype.builder() + .state(newState) + .blockEntity( + Sponge.game().registry(RegistryTypes.BLOCK_ENTITY_TYPE) + .value(ResourceKey.resolve(baseBlock.getNbtId())) + ) + .blockEntityData(NbtAdapter.adaptFromWorldEdit(nbt)) + .build() + .apply(ServerLocation.of(world, position.x(), position.y(), position.z())); + } + } - private static final BlockSnapshot.Builder builder = BlockSnapshot.builder(); + return true; + } @Override - public > boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException { + public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { checkNotNull(position); - checkNotNull(block); - World world = getWorldChecked(); + worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); - // First set the block - Vector3i pos = new Vector3i(position.getX(), position.getY(), position.getZ()); - BlockState newState = getBlockState(block); + return Sets.intersection( + SpongeWorldEdit.inst().getInternalPlatform().getSupportedSideEffects(), + sideEffectSet.getSideEffectsToApply() + ); + } - BlockSnapshot snapshot = builder.reset() - .blockState(newState) - .position(pos) - .world(world.getProperties()) - .build(); + @Override + public boolean clearContainerBlockContents(BlockVector3 position) { + getWorld().removeBlockEntity(position.x(), position.y(), position.z()); + return true; + } - snapshot.restore(true, notifyAndLight ? BlockChangeFlags.ALL : BlockChangeFlags.NONE); + @Override + public boolean regenerate(Region region, Extent extent, RegenOptions options) { + Server server = Sponge.server(); + + final String id = "worldedittemp_" + getWorld().key().value(); + final ResourceKey key = ResourceKey.of("worldedit", id); + + WorldGenerationConfig worldGenConfig = WorldGenerationConfig.builder() + .from(getWorld().properties().worldGenerationConfig()) + .seed(options.getSeed().orElse(getWorld().properties().worldGenerationConfig().seed())) + .build(); + + WorldArchetypeType worldArchetypeType = WorldArchetypeType.builder() + .chunkGenerator(getWorld().generator()) + .worldType(getWorld().worldType()) + .build(); + + WorldArchetype worldArchetype = WorldArchetype.builder() + .generationConfig(worldGenConfig) + .type(worldArchetypeType) + .build(); + + ServerWorldProperties.LoadOptions loadOptions = ServerWorldProperties.LoadOptions.create( + worldArchetype, + (properties) -> { + properties.copyFrom(getWorld().properties()); + properties.offer(Keys.SERIALIZATION_BEHAVIOR, SerializationBehavior.NONE); + properties.offer(Keys.IS_LOAD_ON_STARTUP, false); + } + ); - // Create the TileEntity - if (block instanceof BaseBlock && ((BaseBlock) block).hasNbtData()) { - // Kill the old TileEntity - world.getTileEntity(pos).ifPresent(tileEntity -> applyTileEntityData(tileEntity, (BaseBlock) block)); + ServerWorld tempWorld; + try { + tempWorld = server.worldManager().loadWorld(key, loadOptions).get().get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Failed to load temp world", e); + return false; + } + + try { + // Pre-gen all the chunks + // We need to also pull one more chunk in every direction + CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 16, 16), region.getMaximumPoint().add(16, 16, 16)); + for (BlockVector3 chunk : expandedPreGen.getChunkCubes()) { + tempWorld.loadChunk(chunk.x(), chunk.y(), chunk.z(), true); + } + + World from = SpongeAdapter.adapt(tempWorld); + for (BlockVector3 vec : region) { + extent.setBlock(vec, from.getFullBlock(vec)); + if (options.shouldRegenBiomes()) { + extent.setBiome(vec, from.getBiome(vec)); + } + } + } catch (WorldEditException e) { + throw new RuntimeException(e); + } finally { + // Remove temp world + server.worldManager().unloadWorld(key).thenRun(() -> server.worldManager().deleteWorld(key)); } return true; } + + @SuppressWarnings("deprecation") + @Nullable + private static net.minecraft.resources.ResourceKey createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType type) { + return switch (type) { + // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields + case TREE -> TreePlacements.OAK_CHECKED; + case BIG_TREE -> TreePlacements.FANCY_OAK_CHECKED; + case REDWOOD -> TreePlacements.SPRUCE_CHECKED; + case TALL_REDWOOD -> TreePlacements.MEGA_SPRUCE_CHECKED; + case MEGA_REDWOOD -> TreePlacements.MEGA_PINE_CHECKED; + case BIRCH -> TreePlacements.BIRCH_CHECKED; + case JUNGLE -> TreePlacements.MEGA_JUNGLE_TREE_CHECKED; + case SMALL_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; + case SHORT_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; + case JUNGLE_BUSH -> TreePlacements.JUNGLE_BUSH; + case SWAMP -> TreePlacements.OAK_CHECKED; + case ACACIA -> TreePlacements.ACACIA_CHECKED; + case DARK_OAK -> TreePlacements.DARK_OAK_CHECKED; + case TALL_BIRCH -> TreePlacements.SUPER_BIRCH_BEES_0002; + case WARPED_FUNGUS -> TreePlacements.WARPED_FUNGI; + case CRIMSON_FUNGUS -> TreePlacements.CRIMSON_FUNGI; + case CHORUS_PLANT -> EndPlacements.CHORUS_PLANT; + case MANGROVE -> TreePlacements.MANGROVE_CHECKED; + case TALL_MANGROVE -> TreePlacements.TALL_MANGROVE_CHECKED; + case CHERRY -> TreePlacements.CHERRY_CHECKED; + case PALE_OAK -> TreePlacements.PALE_OAK_CHECKED; + case PALE_OAK_CREAKING -> TreePlacements.PALE_OAK_CREAKING_CHECKED; + case RANDOM -> createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(com.sk89q.worldedit.util.TreeGenerator.TreeType.values().length)]); + default -> null; + }; + } + + @SuppressWarnings("deprecation") @Override - public boolean notifyAndLightBlock(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType) throws WorldEditException { - // TODO Move this to adapter - return false; + public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) { + ServerLevel world = (ServerLevel) getWorld(); + PlacedFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) + .map(k -> world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(k)) + .orElse(null); + return generator != null && generator.place( + world, world.getChunkSource().getGenerator(), random, + new BlockPos(position.x(), position.y(), position.z()) + ); } @Override - public boolean regenerate(Region region, EditSession editSession) { - return false; + public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { + ServerLevel world = (ServerLevel) getWorld(); + PlacedFeature generator = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); + return generator != null && generator.place( + world, world.getChunkSource().getGenerator(), random, + new BlockPos(position.x(), position.y(), position.z()) + ); } @Override public int getBlockLightLevel(BlockVector3 position) { checkNotNull(position); - BlockState state = getWorld().getBlock(new Vector3i(position.getX(), position.getY(), position.getZ())); - - Optional groundLuminanceProperty = state.getProperty(GroundLuminanceProperty.class); - Optional skyLuminanceProperty = state.getProperty(SkyLuminanceProperty.class); - - if (!groundLuminanceProperty.isPresent() || !skyLuminanceProperty.isPresent()) { - return 0; - } - - //noinspection ConstantConditions - return (int) Math.max(groundLuminanceProperty.get().getValue(), skyLuminanceProperty.get().getValue()); + int skyLight = getWorld().light(LightTypes.SKY, position.x(), position.y(), position.z()); + int groundLight = getWorld().light(LightTypes.BLOCK, position.x(), position.y(), position.z()); + return Math.max(skyLight, groundLight); } @Override - public BiomeType getBiome(BlockVector2 position) { + public BiomeType getBiome(BlockVector3 position) { checkNotNull(position); - return SpongeAdapter.adapt(getWorld().getBiome(position.getBlockX(), 0, position.getBlockZ())); + return BiomeType.REGISTRY.get( + getWorld().registry(RegistryTypes.BIOME) + .valueKey(getWorld().biome(position.x(), position.y(), position.z())) + .asString() + ); } @Override - public boolean setBiome(BlockVector2 position, BiomeType biome) { + public boolean setBiome(BlockVector3 position, BiomeType biome) { checkNotNull(position); checkNotNull(biome); - getWorld().setBiome(position.getBlockX(), 0, position.getBlockZ(), SpongeAdapter.adapt(biome)); + getWorld().setBiome( + position.x(), position.y(), position.z(), + getWorld().registry(RegistryTypes.BIOME).value( + ResourceKey.resolve(biome.id()) + ) + ); return true; } @@ -228,18 +422,32 @@ public void dropItem(Vector3 position, BaseItemStack item) { return; } - org.spongepowered.api.entity.Entity entity = getWorld().createEntity( - EntityTypes.ITEM, - new Vector3d(position.getX(), position.getY(), position.getZ()) + Item itemEntity = getWorld().createEntity( + EntityTypes.ITEM, + new Vector3d(position.x(), position.y(), position.z()) ); - entity.offer(Keys.REPRESENTED_ITEM, SpongeWorldEdit.toSpongeItemStack(item).createSnapshot()); - getWorld().spawnEntity(entity); + itemEntity.item().set( + SpongeAdapter.adapt(item).asImmutable() + ); + getWorld().spawnEntity(itemEntity); } @Override public void simulateBlockMine(BlockVector3 position) { - // TODO + getWorld().destroyBlock( + new Vector3i(position.x(), position.y(), position.z()), + true + ); + } + + @Override + public boolean canPlaceAt(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState blockState) { + return ((net.minecraft.world.level.block.state.BlockState) SpongeAdapter.adapt(blockState)) + .canSurvive( + ((LevelReader) getWorld()), + new BlockPos(position.x(), position.y(), position.z()) + ); } @Override @@ -247,104 +455,117 @@ public int hashCode() { return getWorld().hashCode(); } + @Override + public int getMaxY() { + return getWorld().max().y(); + } + + @Override + public int getMinY() { + return getWorld().min().y(); + } + @Override public boolean equals(Object o) { if (o == null) { return false; - } else if ((o instanceof SpongeWorld)) { - SpongeWorld other = ((SpongeWorld) o); - World otherWorld = other.worldRef.get(); - World thisWorld = worldRef.get(); + } else if ((o instanceof SpongeWorld other)) { + ServerWorld otherWorld = other.worldRef.get(); + ServerWorld thisWorld = worldRef.get(); return otherWorld != null && otherWorld.equals(thisWorld); } else { return o instanceof com.sk89q.worldedit.world.World - && ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); + && ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); } } @Override public List getEntities(Region region) { - List entities = new ArrayList<>(); - for (org.spongepowered.api.entity.Entity entity : getWorld().getEntities()) { - org.spongepowered.api.world.Location loc = entity.getLocation(); - if (region.contains(BlockVector3.at(loc.getX(), loc.getY(), loc.getZ()))) { - entities.add(new SpongeEntity(entity)); - } - } - return entities; + return getWorld() + .entityStream( + SpongeAdapter.adaptVector3i(region.getMinimumPoint()), + SpongeAdapter.adaptVector3i(region.getMaximumPoint()), + // We don't need to force load or clone to copy entities + StreamOptions.builder() + .setCarbonCopy(false) + .setLoadingStyle(StreamOptions.LoadingStyle.NONE) + .build() + ) + .toStream() + .map(ve -> new SpongeEntity(ve.type())) + .collect(Collectors.toList()); } @Override public List getEntities() { - List entities = new ArrayList<>(); - for (org.spongepowered.api.entity.Entity entity : getWorld().getEntities()) { - entities.add(new SpongeEntity(entity)); - } - return entities; + return getWorld().entities().stream() + .map(SpongeEntity::new) + .collect(Collectors.toList()); } - protected abstract void applyEntityData(org.spongepowered.api.entity.Entity entity, BaseEntity data); - @Nullable @Override public Entity createEntity(Location location, BaseEntity entity) { - World world = getWorld(); - - EntityType entityType = Sponge.getRegistry().getType(EntityType.class, entity.getType().getId()).get(); - Vector3d pos = new Vector3d(location.getX(), location.getY(), location.getZ()); - - org.spongepowered.api.entity.Entity newEnt = world.createEntity(entityType, pos); - if (entity.hasNbtData()) { - applyEntityData(newEnt, entity); + Optional> entityType = Sponge.game().registry(RegistryTypes.ENTITY_TYPE) + .findValue(ResourceKey.resolve(entity.getType().id())); + if (entityType.isEmpty()) { + return null; } - - // Overwrite any data set by the NBT application - Vector3 dir = location.getDirection(); - - newEnt.setLocationAndRotation( - new org.spongepowered.api.world.Location<>(getWorld(), pos), - new Vector3d(dir.getX(), dir.getY(), dir.getZ()) - ); - - if (world.spawnEntity(newEnt)) { - return new SpongeEntity(newEnt); + EntityArchetype.Builder builder = EntityArchetype.builder().type(entityType.get()); + var nativeTag = entity.getNbt(); + if (nativeTag != null) { + builder.entityData(NbtAdapter.adaptFromWorldEdit(nativeTag)); } + return builder.build().apply(SpongeAdapter.adapt(location)).map(SpongeEntity::new).orElse(null); + } - return null; + @Override + public void sendBiomeUpdates(Iterable chunks) { + List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); + for (BlockVector2 chunk : chunks) { + nativeChunks.add(((Level) getWorld()).getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); + } + ((ServerLevel) getWorld()).getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } @Override public WeatherType getWeather() { - return WeatherTypes.get(getWorld().getWeather().getId()); + return WeatherTypes.get( + getWorld().weather().type().key(RegistryTypes.WEATHER_TYPE).asString() + ); } @Override public long getRemainingWeatherDuration() { - return getWorld().getRemainingDuration(); + return getWorld().weather().remainingDuration().ticks(); } @Override public void setWeather(WeatherType weatherType) { - getWorld().setWeather(Sponge.getRegistry().getType(Weather.class, weatherType.getId()).get()); + getWorld().setWeather( + Sponge.game().registry(RegistryTypes.WEATHER_TYPE).value( + ResourceKey.resolve(weatherType.id()) + ) + ); } @Override public void setWeather(WeatherType weatherType, long duration) { - getWorld().setWeather(Sponge.getRegistry().getType(Weather.class, weatherType.getId()).get(), duration); + getWorld().setWeather( + Sponge.game().registry(RegistryTypes.WEATHER_TYPE).value( + ResourceKey.resolve(weatherType.id()) + ), + Ticks.of(duration) + ); } @Override public BlockVector3 getSpawnPosition() { - return SpongeAdapter.asBlockVector(getWorld().getSpawnLocation()); + return SpongeAdapter.adaptVector3i(getWorld().properties().spawnPosition()); } - /** - * Thrown when the reference to the world is lost. - */ - private static class WorldReferenceLostException extends WorldEditException { - private WorldReferenceLostException(String message) { - super(message); - } + @Override + public boolean isValid() { + return worldRef.get() != null; } - } diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java index 194ffee159..ecc6506007 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java @@ -19,287 +19,306 @@ package com.sk89q.worldedit.sponge; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; - +import com.google.common.base.Joiner; import com.google.inject.Inject; -import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.command.util.PermissionCondition; +import com.sk89q.worldedit.event.platform.CommandEvent; +import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; -import com.sk89q.worldedit.event.platform.SessionIdleEvent; +import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; +import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extension.platform.PlatformManager; import com.sk89q.worldedit.internal.anvil.ChunkDeleter; -import com.sk89q.worldedit.sponge.adapter.AdapterLoadException; -import com.sk89q.worldedit.sponge.adapter.SpongeImplAdapter; -import com.sk89q.worldedit.sponge.adapter.SpongeImplLoader; +import com.sk89q.worldedit.internal.command.CommandUtil; +import com.sk89q.worldedit.registry.Registries; import com.sk89q.worldedit.sponge.config.SpongeConfiguration; -import org.bstats.sponge.Metrics2; +import com.sk89q.worldedit.world.biome.BiomeCategory; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.generation.TreeType; +import com.sk89q.worldedit.world.item.ItemCategory; +import net.kyori.adventure.audience.Audience; import org.apache.logging.log4j.Logger; +import org.bstats.sponge.Metrics; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; -import org.spongepowered.api.block.BlockSnapshot; import org.spongepowered.api.block.BlockType; -import org.spongepowered.api.block.BlockTypes; -import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.block.entity.BlockEntity; +import org.spongepowered.api.block.entity.CommandBlock; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.parameter.ArgumentReader; import org.spongepowered.api.config.ConfigDir; -import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.block.InteractBlockEvent; -import org.spongepowered.api.event.filter.cause.Root; -import org.spongepowered.api.event.game.state.GameAboutToStartServerEvent; -import org.spongepowered.api.event.game.state.GameInitializationEvent; -import org.spongepowered.api.event.game.state.GamePostInitializationEvent; -import org.spongepowered.api.event.game.state.GamePreInitializationEvent; -import org.spongepowered.api.event.game.state.GameStartedServerEvent; -import org.spongepowered.api.event.game.state.GameStoppingServerEvent; -import org.spongepowered.api.event.item.inventory.InteractItemEvent; -import org.spongepowered.api.event.network.ClientConnectionEvent; -import org.spongepowered.api.item.ItemType; -import org.spongepowered.api.item.inventory.ItemStack; -import org.spongepowered.api.plugin.Plugin; -import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; +import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; +import org.spongepowered.api.event.lifecycle.StartedEngineEvent; +import org.spongepowered.api.event.lifecycle.StartingEngineEvent; +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; +import org.spongepowered.api.registry.RegistryTypes; import org.spongepowered.api.scheduler.Task; -import org.spongepowered.api.world.Location; -import org.spongepowered.api.world.World; +import org.spongepowered.api.world.LocatableBlock; +import org.spongepowered.api.world.generation.feature.FeatureTypes; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.builtin.jvm.Plugin; -import java.io.File; -import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; +import static java.util.stream.Collectors.toList; /** * The Sponge implementation of WorldEdit. */ -@Plugin(id = SpongeWorldEdit.MOD_ID, name = "WorldEdit", - description = "WorldEdit is an easy-to-use in-game world editor for Minecraft", - url = "https://enginehub.org/worldedit/") +@Plugin(SpongeWorldEdit.MOD_ID) public class SpongeWorldEdit { - @Inject - private Logger logger; - - private Metrics2 metrics; - public static final String MOD_ID = "worldedit"; private static final int BSTATS_PLUGIN_ID = 3329; - private SpongePermissionsProvider provider; - - @Inject - private PluginContainer container; - private static SpongeWorldEdit inst; - public static PluginContainer container() { - return inst.container; - } - public static SpongeWorldEdit inst() { return inst; } - private SpongePlatform platform; - private SpongeImplAdapter spongeAdapter; + private final Logger logger; + private final PluginContainer container; + private final SpongeConfiguration config; + private final Path workingDir; - @Inject - private SpongeConfiguration config; - - @Inject @ConfigDir(sharedRoot = false) - private File workingDir; + private SpongePermissionsProvider provider; + private SpongePlatform platform; @Inject - public SpongeWorldEdit(Metrics2.Factory metricsFactory) { + public SpongeWorldEdit(Logger logger, + PluginContainer container, + SpongeConfiguration config, + Metrics.Factory metricsFactory, + @ConfigDir(sharedRoot = false) + Path workingDir) { + this.logger = logger; + this.container = container; + this.config = config; + this.workingDir = workingDir; + metricsFactory.make(BSTATS_PLUGIN_ID); inst = this; - metrics = metricsFactory.make(BSTATS_PLUGIN_ID); } @Listener - public void preInit(GamePreInitializationEvent event) { - // Load configuration - config.load(); + public void onPluginConstruction(ConstructPluginEvent event) { + this.platform = new SpongePlatform(this); - Task.builder().interval(30, TimeUnit.SECONDS).execute(ThreadSafeCache.getInstance()).submit(this); - } + WorldEdit.getInstance().getPlatformManager().register(platform); - @Listener - public void init(GameInitializationEvent event) { - CUIChannelHandler.init(); - } + this.provider = new SpongePermissionsProvider(); + + event.game().eventManager().registerListeners( + container, + new CUIChannelHandler.RegistrationHandler(), + MethodHandles.lookup() + ); + + event.game().eventManager().registerListeners( + container, + new SpongeWorldEditListener(this), + MethodHandles.lookup() + ); - @Listener - public void postInit(GamePostInitializationEvent event) { logger.info("WorldEdit for Sponge (version " + getInternalVersion() + ") is loaded"); } @Listener - public void serverAboutToStart(GameAboutToStartServerEvent event) { - if (this.platform != null) { - logger.warn("GameAboutToStartServerEvent occurred when GameStoppingServerEvent hasn't"); - WorldEdit.getInstance().getPlatformManager().unregister(platform); - } - - final Path delChunks = workingDir.toPath().resolve(DELCHUNKS_FILE_NAME); + public void serverStarting(StartingEngineEvent event) { + final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME); if (Files.exists(delChunks)) { ChunkDeleter.runFromFile(delChunks, true); } + } - this.platform = new SpongePlatform(this); - this.provider = new SpongePermissionsProvider(); - - for (BlockType blockType : Sponge.getRegistry().getAllOf(BlockType.class)) { - // TODO Handle blockstate stuff - String id = blockType.getId(); + @Listener + public void serverStarted(StartedEngineEvent event) { + event.engine().scheduler().submit(Task.builder() + .plugin(container) + .interval(30, TimeUnit.SECONDS) + .execute(ThreadSafeCache.getInstance()) + .build()); + + event.game().registry(RegistryTypes.BLOCK_TYPE).streamEntries().forEach(blockType -> { + String id = blockType.key().asString(); if (!com.sk89q.worldedit.world.block.BlockType.REGISTRY.keySet().contains(id)) { - com.sk89q.worldedit.world.block.BlockType.REGISTRY.register(id, new com.sk89q.worldedit.world.block.BlockType(id)); + com.sk89q.worldedit.world.block.BlockType.REGISTRY.register(id, new com.sk89q.worldedit.world.block.BlockType( + id, + input -> { + BlockType spongeBlockType = Sponge.game().registry(RegistryTypes.BLOCK_TYPE).value( + ResourceKey.resolve(input.getBlockType().id()) + ); + return SpongeAdapter.adapt(spongeBlockType.defaultState()); + } + )); } - } + }); - for (ItemType itemType : Sponge.getRegistry().getAllOf(ItemType.class)) { - String id = itemType.getId(); + event.game().registry(RegistryTypes.ITEM_TYPE).streamEntries().forEach(itemType -> { + String id = itemType.key().asString(); if (!com.sk89q.worldedit.world.item.ItemType.REGISTRY.keySet().contains(id)) { com.sk89q.worldedit.world.item.ItemType.REGISTRY.register(id, new com.sk89q.worldedit.world.item.ItemType(id)); } + }); + + event.game().registry(RegistryTypes.ENTITY_TYPE).streamEntries().forEach(entityType -> { + String id = entityType.key().asString(); + if (!com.sk89q.worldedit.world.entity.EntityType.REGISTRY.keySet().contains(id)) { + com.sk89q.worldedit.world.entity.EntityType.REGISTRY.register(id, new com.sk89q.worldedit.world.entity.EntityType(id)); + } + }); + + for (ServerWorld world : event.engine().worldManager().worlds()) { + world.registry(RegistryTypes.BIOME).streamEntries().forEach(biomeType -> { + String id = biomeType.key().asString(); + if (!BiomeType.REGISTRY.keySet().contains(id)) { + BiomeType.REGISTRY.register(id, new BiomeType(id)); + } + }); } - WorldEdit.getInstance().getPlatformManager().register(platform); + // Disabled until https://github.com/SpongePowered/SpongeAPI/issues/2520 is resolved + // Will also need implementations in SpongeWorld to do placement + // Sponge.server().registry(RegistryTypes.FEATURE).streamEntries().forEach(feature -> { + // String id = feature.key().asString(); + // if (!ConfiguredFeatureType.REGISTRY.keySet().contains(id)) { + // ConfiguredFeatureType.REGISTRY.register(id, new ConfiguredFeatureType(id)); + // } + // }); + // Sponge.server().registry(RegistryTypes.STRUCTURE).streamEntries().forEach(structure -> { + // String id = structure.key().asString(); + // if (!StructureType.REGISTRY.keySet().contains(id)) { + // StructureType.REGISTRY.register(id, new StructureType(id)); + // } + // }); + + event.game().registry(RegistryTypes.PLACED_FEATURE).streamEntries().forEach(feature -> { + String id = feature.key().asString(); + var underlyingFeatureType = feature.value().feature().type(); + if (underlyingFeatureType.equals(FeatureTypes.TREE) || underlyingFeatureType.equals(FeatureTypes.FALLEN_TREE) || underlyingFeatureType.equals(FeatureTypes.CORAL_TREE)) { + if (!TreeType.REGISTRY.keySet().contains(id)) { + TreeType.REGISTRY.register(id, new TreeType(id)); + } + } + }); + event.game().registry(RegistryTypes.BLOCK_TYPE).tags().forEach(blockTypeTag -> { + String id = blockTypeTag.key().asString(); + if (!BlockCategory.REGISTRY.keySet().contains(id)) { + BlockCategory.REGISTRY.register(id, new BlockCategory(id)); + } + }); + event.game().registry(RegistryTypes.ITEM_TYPE).tags().forEach(itemTypeTag -> { + String id = itemTypeTag.key().asString(); + if (!ItemCategory.REGISTRY.keySet().contains(id)) { + ItemCategory.REGISTRY.register(id, new ItemCategory(id)); + } + }); + event.game().registry(RegistryTypes.BIOME).tags().forEach(biomeTag -> { + String id = biomeTag.key().asString(); + if (!BiomeCategory.REGISTRY.keySet().contains(id)) { + BiomeCategory.REGISTRY.register(id, new BiomeCategory(id, () -> event.game().registry(RegistryTypes.BIOME).taggedValues(biomeTag).map(SpongeAdapter::adapt).collect(Collectors.toSet()))); + } + }); + + Registries.get(""); + + config.load(); + WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); } @Listener - public void serverStopping(GameStoppingServerEvent event) { + public void serverStopping(StoppingEngineEvent event) { WorldEdit worldEdit = WorldEdit.getInstance(); worldEdit.getSessionManager().unload(); - worldEdit.getPlatformManager().unregister(platform); + WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); } @Listener - public void serverStarted(GameStartedServerEvent event) { - WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); - - loadAdapter(); - } - - private void loadAdapter() { - WorldEdit worldEdit = WorldEdit.getInstance(); - - // Attempt to load a Sponge adapter - SpongeImplLoader adapterLoader = new SpongeImplLoader(); - - try { - adapterLoader.addFromPath(getClass().getClassLoader()); - } catch (IOException e) { - logger.warn("Failed to search path for Sponge adapters"); + public void registerCommand(RegisterCommandEvent event) { + WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); + PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); + Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); + if (commandsPlatform != platform || !platform.isHookingEvents()) { + // We're not in control of commands/events -- do not register. + return; } - try { - adapterLoader.addFromJar(container.getSource().get().toFile()); - } catch (IOException e) { - logger.warn("Failed to search " + container.getSource().get().toFile() + " for Sponge adapters", e); - } - try { - spongeAdapter = adapterLoader.loadAdapter(); - logger.info("Using " + spongeAdapter.getClass().getCanonicalName() + " as the Sponge adapter"); - } catch (AdapterLoadException e) { - Platform platform = worldEdit.getPlatformManager().queryCapability(Capability.WORLD_EDITING); - if (platform instanceof SpongePlatform) { - logger.warn(e.getMessage()); - } else { - logger.info("WorldEdit could not find a Sponge adapter for this MC version, " + - "but it seems that you have another implementation of WorldEdit installed (" + platform.getPlatformName() + ") " + - "that handles the world editing."); + List commands = manager.getPlatformCommandManager().getCommandManager() + .getAllCommands().toList(); + for (org.enginehub.piston.Command command : commands) { + registerAdaptedCommand(event, command); + + Set perms = command.getCondition().as(PermissionCondition.class) + .map(PermissionCondition::getPermissions) + .orElseGet(Collections::emptySet); + if (!perms.isEmpty()) { + perms.forEach(getPermissionsProvider()::registerPermission); } } } - public SpongeImplAdapter getAdapter() { - return this.spongeAdapter; - } - - @Listener - public void onPlayerItemInteract(InteractItemEvent.Secondary event, @Root Player spongePlayer) { - if (platform == null) { - return; + private String rebuildArguments(String commandLabel, String args) { + int plSep = commandLabel.indexOf(':'); + if (plSep >= 0 && plSep < commandLabel.length() + 1) { + commandLabel = commandLabel.substring(plSep + 1); } - if (!platform.isHookingEvents()) return; // We have to be told to catch these events - - WorldEdit we = WorldEdit.getInstance(); + StringBuilder sb = new StringBuilder("/").append(commandLabel); - SpongePlayer player = wrapPlayer(spongePlayer); - if (we.handleRightClick(player)) { - event.setCancelled(true); + String[] split = args.split(" ", -1); + if (split.length > 0) { + sb.append(" "); } + return Joiner.on(" ").appendTo(sb, split).toString(); } - @Listener - public void onPlayerInteract(InteractBlockEvent event, @Root Player spongePlayer) { - if (platform == null) { - return; - } - - if (!platform.isHookingEvents()) return; // We have to be told to catch these events - - WorldEdit we = WorldEdit.getInstance(); - - SpongePlayer player = wrapPlayer(spongePlayer); - com.sk89q.worldedit.world.World world = player.getWorld(); - - BlockSnapshot targetBlock = event.getTargetBlock(); - Optional> optLoc = targetBlock.getLocation(); - - BlockType interactedType = targetBlock.getState().getType(); - if (event instanceof InteractBlockEvent.Primary) { - if (interactedType != BlockTypes.AIR) { - if (!optLoc.isPresent()) { - return; - } - - Location loc = optLoc.get(); - com.sk89q.worldedit.util.Location pos = new com.sk89q.worldedit.util.Location( - world, loc.getX(), loc.getY(), loc.getZ()); - - if (we.handleBlockLeftClick(player, pos)) { - event.setCancelled(true); - } - - if (we.handleArmSwing(player)) { - event.setCancelled(true); - } - } else { - if (we.handleArmSwing(player)) { - event.setCancelled(true); - } - } - } else if (event instanceof InteractBlockEvent.Secondary) { - if (!optLoc.isPresent()) { - return; - } - - Location loc = optLoc.get(); - com.sk89q.worldedit.util.Location pos = new com.sk89q.worldedit.util.Location( - world, loc.getX(), loc.getY(), loc.getZ()); - - if (we.handleBlockRightClick(player, pos)) { - event.setCancelled(true); + private void registerAdaptedCommand(RegisterCommandEvent event, org.enginehub.piston.Command command) { + CommandAdapter adapter = new CommandAdapter(command) { + @Override + public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { + CommandEvent weEvent = new CommandEvent(SpongeWorldEdit.inst().wrapCommandCause(cause), rebuildArguments(command.getName(), arguments.remaining()).trim()); + WorldEdit.getInstance().getEventBus().post(weEvent); + return weEvent.isCancelled() ? CommandResult.success() : CommandResult.builder().build(); } - if (we.handleRightClick(player)) { - event.setCancelled(true); + @Override + public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { + String args = rebuildArguments(command.getName(), arguments.remaining()); + CommandSuggestionEvent weEvent = new CommandSuggestionEvent(SpongeWorldEdit.inst().wrapCommandCause(cause), args); + WorldEdit.getInstance().getEventBus().post(weEvent); + return CommandUtil.fixSuggestions(args, weEvent.getSuggestions()) + .stream().map(CommandCompletion::of).collect(toList()); } - } + }; + event.register( + container, adapter, command.getName(), command.getAliases().toArray(new String[0]) + ); } - @Listener - public void onPlayerQuit(ClientConnectionEvent.Disconnect event) { - WorldEdit.getInstance().getEventBus() - .post(new SessionIdleEvent(new SpongePlayer.SessionKeyImpl(event.getTargetEntity()))); - } - - public static ItemStack toSpongeItemStack(BaseItemStack item) { - return inst().getAdapter().makeSpongeStack(item); + public PluginContainer getPluginContainer() { + return container; } /** @@ -311,46 +330,27 @@ SpongeConfiguration getConfig() { return this.config; } - /** - * Get the WorldEdit proxy for the given player. - * - * @param player the player - * @return the WorldEdit player - */ - public SpongePlayer wrapPlayer(Player player) { - checkNotNull(player); - return new SpongePlayer(platform, player); - } - - public Actor wrapCommandSource(CommandSource sender) { - if (sender instanceof Player) { - return wrapPlayer((Player) sender); + public Actor wrapCommandCause(CommandCause cause) { + Object rootCause = cause.root(); + if (rootCause instanceof ServerPlayer serverPlayer) { + return SpongeAdapter.adapt(serverPlayer); + } + if (rootCause instanceof LocatableBlock locatableBlock) { + Optional optionalBlockEntity = locatableBlock.world().blockEntity(locatableBlock.blockPosition()); + if (optionalBlockEntity.isPresent()) { + BlockEntity blockEntity = optionalBlockEntity.get(); + if (blockEntity instanceof CommandBlock commandBlock) { + return new SpongeBlockCommandSender(this, commandBlock); + } + } + } + if (rootCause instanceof Audience audience) { + return new SpongeCommandSender(audience); } - return new SpongeCommandSender(this, sender); - } - - /** - * Get the session for a player. - * - * @param player the player - * @return the session - */ - public LocalSession getSession(Player player) { - checkNotNull(player); - return WorldEdit.getInstance().getSessionManager().get(wrapPlayer(player)); + throw new UnsupportedOperationException("Cannot wrap " + rootCause.getClass()); } - /** - * Get the WorldEdit proxy for the given world. - * - * @param world the world - * @return the WorldEdit world - */ - public SpongeWorld getWorld(World world) { - checkNotNull(world); - return getAdapter().getWorld(world); - } /** * Get the WorldEdit proxy for the platform. @@ -361,12 +361,16 @@ public Platform getPlatform() { return this.platform; } + SpongePlatform getInternalPlatform() { + return this.platform; + } + /** * Get the working directory where WorldEdit's files are stored. * * @return the working directory */ - public File getWorkingDir() { + public Path getWorkingDir() { return this.workingDir; } @@ -376,7 +380,7 @@ public File getWorkingDir() { * @return a version string */ String getInternalVersion() { - return container.getVersion().orElse("Unknown"); + return container.metadata().version().toString(); } public void setPermissionsProvider(SpongePermissionsProvider provider) { @@ -386,5 +390,4 @@ public void setPermissionsProvider(SpongePermissionsProvider provider) { public SpongePermissionsProvider getPermissionsProvider() { return provider; } - } From 638848b002bfc6bfcf89ec1c85ee47536e4754a4 Mon Sep 17 00:00:00 2001 From: SirYwell Date: Fri, 1 May 2026 10:11:09 +0200 Subject: [PATCH 2/2] revert changes or unsupported platforms --- .../sk89q/worldedit/fabric/FabricWorld.java | 723 ++++++----------- .../worldedit/fabric/FabricWorldEdit.java | 365 +++------ .../worldedit/neoforge/NeoForgeWorld.java | 765 ------------------ .../worldedit/neoforge/NeoForgeWorldEdit.java | 501 ------------ .../sk89q/worldedit/sponge/SpongeWorld.java | 527 ++++-------- .../worldedit/sponge/SpongeWorldEdit.java | 473 ++++++----- 6 files changed, 762 insertions(+), 2592 deletions(-) delete mode 100644 worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java delete mode 100644 worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java index 9f8ea90c44..e93ae036ef 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java @@ -19,14 +19,14 @@ package com.sk89q.worldedit.fabric; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import com.google.common.collect.Streams; -import com.google.common.util.concurrent.Futures; +import com.google.common.io.Files; +import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; @@ -34,123 +34,102 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.fabric.internal.ExtendedMinecraftServer; -import com.sk89q.worldedit.fabric.internal.FabricEntity; -import com.sk89q.worldedit.fabric.internal.FabricServerLevelDelegateProxy; import com.sk89q.worldedit.fabric.internal.FabricWorldNativeAccess; import com.sk89q.worldedit.fabric.internal.NBTConverter; -import com.sk89q.worldedit.function.mask.AbstractExtentMask; -import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.concurrency.LazyReference; -import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.world.AbstractWorld; -import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; 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.generation.ConfiguredFeatureType; -import com.sk89q.worldedit.world.generation.StructureType; -import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; -import net.minecraft.Util; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.SectionPos; -import net.minecraft.core.registries.Registries; -import net.minecraft.data.worldgen.placement.EndPlacements; -import net.minecraft.data.worldgen.placement.TreePlacements; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.LeavesBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ItemEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.ProblemReporter; -import net.minecraft.util.RandomSource; -import net.minecraft.world.Clearable; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.EntitySpawnReason; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.UseOnContext; -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.LiquidBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkSource; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.chunk.PalettedContainer; -import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.minecraft.world.level.dimension.LevelStem; -import net.minecraft.world.level.levelgen.WorldOptions; -import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; -import net.minecraft.world.level.levelgen.placement.PlacedFeature; -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.DerivedLevelData; -import net.minecraft.world.level.storage.LevelData; -import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.PrimaryLevelData; -import net.minecraft.world.level.storage.ServerLevelData; -import net.minecraft.world.level.storage.TagValueOutput; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.BlockHitResult; -import org.enginehub.linbus.tree.LinCompoundTag; - +import net.minecraft.server.WorldGenerationProgressListener; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Clearable; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.WorldSaveHandler; +import net.minecraft.world.biome.DefaultBiomeFeatures; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkManager; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.gen.feature.BirchTreeFeature; +import net.minecraft.world.gen.feature.DarkOakTreeFeature; +import net.minecraft.world.gen.feature.DefaultFeatureConfig; +import net.minecraft.world.gen.feature.Feature; +import net.minecraft.world.gen.feature.FeatureConfig; +import net.minecraft.world.gen.feature.HugeBrownMushroomFeature; +import net.minecraft.world.gen.feature.HugeRedMushroomFeature; +import net.minecraft.world.gen.feature.JungleGroundBushFeature; +import net.minecraft.world.gen.feature.JungleTreeFeature; +import net.minecraft.world.gen.feature.LargeOakTreeFeature; +import net.minecraft.world.gen.feature.MegaJungleTreeFeature; +import net.minecraft.world.gen.feature.MegaPineTreeFeature; +import net.minecraft.world.gen.feature.OakTreeFeature; +import net.minecraft.world.gen.feature.PineTreeFeature; +import net.minecraft.world.gen.feature.PlantedFeatureConfig; +import net.minecraft.world.gen.feature.SavannaTreeFeature; +import net.minecraft.world.gen.feature.SpruceTreeFeature; +import net.minecraft.world.gen.feature.SwampTreeFeature; +import net.minecraft.world.level.LevelProperties; + +import java.io.File; import java.lang.ref.WeakReference; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.Locale; import java.util.Optional; -import java.util.OptionalLong; +import java.util.OptionalInt; +import java.util.Random; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; -import javax.annotation.Nullable; +import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; +import javax.annotation.Nullable; /** * An adapter to Minecraft worlds for WorldEdit. */ public class FabricWorld extends AbstractWorld { - private static final RandomSource random = RandomSource.create(); + private static final Random random = new Random(); + private static final int UPDATE = 1, NOTIFY = 2; - private static ResourceLocation getDimensionRegistryKey(Level world) { - return Objects.requireNonNull(world.getServer(), "server cannot be null") - .registryAccess() - .lookupOrThrow(Registries.DIMENSION_TYPE) - .getKey(world.dimensionType()); - } + private static final net.minecraft.block.BlockState JUNGLE_LOG = Blocks.JUNGLE_LOG.getDefaultState(); + private static final net.minecraft.block.BlockState JUNGLE_LEAF = Blocks.JUNGLE_LEAVES.getDefaultState().with(LeavesBlock.PERSISTENT, Boolean.TRUE); + private static final net.minecraft.block.BlockState JUNGLE_SHRUB = Blocks.OAK_LEAVES.getDefaultState().with(LeavesBlock.PERSISTENT, Boolean.TRUE); - private final WeakReference worldRef; + private final WeakReference worldRef; private final FabricWorldNativeAccess worldNativeAccess; /** @@ -158,20 +137,35 @@ private static ResourceLocation getDimensionRegistryKey(Level world) { * * @param world the world */ - FabricWorld(Level world) { + FabricWorld(World world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); this.worldNativeAccess = new FabricWorldNativeAccess(worldRef); } + /** + * Get the underlying handle to the world. + * + * @return the world + * @throws WorldEditException thrown if a reference to the world was lost (i.e. world was unloaded) + */ + public World getWorldChecked() throws WorldEditException { + World world = worldRef.get(); + if (world != null) { + return world; + } else { + throw new WorldReferenceLostException("The reference to the world was lost (i.e. the world may have been unloaded)"); + } + } + /** * Get the underlying handle to the world. * * @return the world * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded) */ - public Level getWorld() { - Level world = worldRef.get(); + public World getWorld() { + World world = worldRef.get(); if (world != null) { return world; } else { @@ -181,31 +175,32 @@ public Level getWorld() { @Override public String getName() { - LevelData levelProperties = getWorld().getLevelData(); - return ((ServerLevelData) levelProperties).getLevelName(); + return getWorld().getLevelProperties().getLevelName(); } @Override - public String id() { - return getName() + "_" + getDimensionRegistryKey(getWorld()); + public String getId() { + return getWorld().getLevelProperties().getLevelName() + .replace(" ", "_").toLowerCase(Locale.ROOT) + + getWorld().dimension.getType().getSuffix(); } @Override public Path getStoragePath() { - final Level world = getWorld(); - MinecraftServer server = world.getServer(); - checkState(server instanceof ExtendedMinecraftServer, "Need a server world"); - return ((ExtendedMinecraftServer) server).getStoragePath(world); + final World world = getWorld(); + if (world instanceof ServerWorld) { + return ((ServerWorld) world).getSaveHandler().getWorldDir().toPath(); + } + return null; } @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { - clearContainerBlockContents(position); return worldNativeAccess.setBlock(position, block, sideEffects); } @Override - public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { + public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); return Sets.intersection(FabricWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); } @@ -213,81 +208,69 @@ public Set applySideEffects(BlockVector3 position, BlockState previo @Override public int getBlockLightLevel(BlockVector3 position) { checkNotNull(position); - return getWorld().getMaxLocalRawBrightness(FabricAdapter.toBlockPos(position)); + return getWorld().getLightLevel(FabricAdapter.toBlockPos(position)); } @Override public boolean clearContainerBlockContents(BlockVector3 position) { checkNotNull(position); - BlockEntity tile = getWorld().getBlockEntity(FabricAdapter.toBlockPos(position)); if ((tile instanceof Clearable)) { - ((Clearable) tile).clearContent(); + ((Clearable) tile).clear(); return true; } return false; } @Override - public BiomeType getBiome(BlockVector3 position) { + public BiomeType getBiome(BlockVector2 position) { checkNotNull(position); - ChunkAccess chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); - return getBiomeInChunk(position, chunk); - } - - private BiomeType getBiomeInChunk(BlockVector3 position, ChunkAccess chunk) { - return FabricAdapter.adapt( - chunk.getNoiseBiome(position.x() >> 2, position.y() >> 2, position.z() >> 2).value() - ); + return FabricAdapter.adapt(getWorld().getBiome(new BlockPos(position.getBlockX(), 0, position.getBlockZ()))); } @Override - public boolean setBiome(BlockVector3 position, BiomeType biome) { + public boolean setBiome(BlockVector2 position, BiomeType biome) { checkNotNull(position); checkNotNull(biome); - ChunkAccess chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); - // Screw it, we know it's really mutable... - var biomeArray = (PalettedContainer>) chunk.getSection(chunk.getSectionIndex(position.y())).getBiomes(); - biomeArray.getAndSetUnchecked( - position.x() & 3, position.y() & 3, position.z() & 3, - getWorld().registryAccess().lookup(Registries.BIOME) - .orElseThrow() - .getOrThrow(ResourceKey.create(Registries.BIOME, ResourceLocation.parse(biome.id()))) - ); - chunk.markUnsaved(); + Chunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4, ChunkStatus.FULL, false); + if (chunk == null) { + return false; + } + chunk.getBiomeArray()[((position.getBlockZ() & 0xF) << 4 | position.getBlockX() & 0xF)] = FabricAdapter.adapt(biome); return true; } - private static final LoadingCache fakePlayers - = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(FabricFakePlayer::new)); + private static final LoadingCache fakePlayers + = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(WorldEditFakePlayer::new)); @Override public boolean useItem(BlockVector3 position, BaseItem item, Direction face) { - ItemStack stack = FabricAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtReference(), 1)); - ServerLevel world = (ServerLevel) getWorld(); - final FabricFakePlayer fakePlayer; + ItemStack stack = FabricAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtData(), 1)); + ServerWorld world = (ServerWorld) getWorld(); + final WorldEditFakePlayer fakePlayer; try { fakePlayer = fakePlayers.get(world); } catch (ExecutionException ignored) { return false; } - fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); - fakePlayer.absSnapTo(position.x(), position.y(), position.z(), + fakePlayer.setStackInHand(Hand.MAIN_HAND, stack); + fakePlayer.setPositionAndAngles(position.getBlockX(), position.getBlockY(), position.getBlockZ(), (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); final BlockPos blockPos = FabricAdapter.toBlockPos(position); final BlockHitResult rayTraceResult = new BlockHitResult(FabricAdapter.toVec3(position), FabricAdapter.adapt(face), blockPos, false); - UseOnContext itemUseContext = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); - InteractionResult used = stack.useOn(itemUseContext); - if (used != InteractionResult.SUCCESS) { + ItemUsageContext itemUseContext = new ItemUsageContext(fakePlayer, Hand.MAIN_HAND, rayTraceResult); + ActionResult used = stack.useOnBlock(itemUseContext); + if (used != ActionResult.SUCCESS) { // try activating the block - used = getWorld().getBlockState(blockPos).useItemOn(stack, world, fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); - } - if (used != InteractionResult.SUCCESS) { - used = stack.use(world, fakePlayer, InteractionHand.MAIN_HAND); + if (getWorld().getBlockState(blockPos).activate(world, fakePlayer, Hand.MAIN_HAND, rayTraceResult)) { + used = ActionResult.SUCCESS; + } else { + used = stack.getItem().use(world, fakePlayer, Hand.MAIN_HAND).getResult(); + } } - return used == InteractionResult.SUCCESS; + return used == ActionResult.SUCCESS; } @Override @@ -299,266 +282,97 @@ public void dropItem(Vector3 position, BaseItemStack item) { return; } - ItemEntity entity = new ItemEntity(getWorld(), position.x(), position.y(), position.z(), FabricAdapter.adapt(item)); - entity.setPickUpDelay(10); - getWorld().addFreshEntity(entity); + ItemEntity entity = new ItemEntity(getWorld(), position.getX(), position.getY(), position.getZ(), FabricAdapter.adapt(item)); + entity.setPickupDelay(10); + getWorld().spawnEntity(entity); } @Override public void simulateBlockMine(BlockVector3 position) { BlockPos pos = FabricAdapter.toBlockPos(position); - getWorld().destroyBlock(pos, true); + getWorld().breakBlock(pos, true); } @Override - public boolean canPlaceAt(BlockVector3 position, BlockState blockState) { - return FabricAdapter.adapt(blockState).canSurvive(getWorld(), FabricAdapter.toBlockPos(position)); - } - - @Override - public boolean regenerate(Region region, Extent extent, RegenOptions options) { + public boolean regenerate(Region region, EditSession editSession) { // Don't even try to regen if it's going to fail. - ChunkSource provider = getWorld().getChunkSource(); - if (!(provider instanceof ServerChunkCache)) { + ChunkManager provider = getWorld().getChunkManager(); + if (!(provider instanceof ServerChunkManager)) { return false; } + File saveFolder = Files.createTempDir(); + // register this just in case something goes wrong + // normally it should be deleted at the end of this method + saveFolder.deleteOnExit(); try { - doRegen(region, extent, options); - } catch (Exception e) { - throw new IllegalStateException("Regen failed", e); - } - - return true; - } - - private void doRegen(Region region, Extent extent, RegenOptions options) throws Exception { - Path tempDir = Files.createTempDirectory("WorldEditWorldGen"); - LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); - try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("WorldEditTempGen")) { - ServerLevel originalWorld = (ServerLevel) getWorld(); - PrimaryLevelData levelProperties = getPrimaryLevelData(originalWorld.getLevelData()); - WorldOptions originalOpts = levelProperties.worldGenOptions(); - - long seed = options.getSeed().orElse(originalWorld.getSeed()); - levelProperties.worldOptions = options.getSeed().isPresent() - ? originalOpts.withSeed(OptionalLong.of(seed)) - : originalOpts; - - ResourceKey worldRegKey = originalWorld.dimension(); - try (ServerLevel serverWorld = new ServerLevel( - originalWorld.getServer(), Util.backgroundExecutor(), session, - ((ServerLevelData) originalWorld.getLevelData()), - worldRegKey, - new LevelStem( - originalWorld.dimensionTypeRegistration(), - originalWorld.getChunkSource().getGenerator() - ), - originalWorld.isDebug(), - seed, - // No spawners are needed for this world. - ImmutableList.of(), - // This controls ticking, we don't need it so set it to false. - false, - originalWorld.getRandomSequences() - )) { - regenForWorld(region, extent, serverWorld, options); - - // drive the server executor until all tasks are popped off - while (originalWorld.getServer().pollTask()) { - Thread.yield(); - } - } finally { - levelProperties.worldOptions = originalOpts; - } - } finally { - SafeFiles.tryHardToDeleteDir(tempDir); - } - } - - private static PrimaryLevelData getPrimaryLevelData(LevelData levelData) { - if (levelData instanceof DerivedLevelData derivedLevelData) { - return getPrimaryLevelData(derivedLevelData.wrapped); - } else if (levelData instanceof PrimaryLevelData primaryLevelData) { - return primaryLevelData; - } else { - throw new IllegalStateException("Unknown level data type: " + levelData.getClass()); - } - } - - private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, - RegenOptions options) throws WorldEditException { - List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); - - // drive executor until loading finishes - serverWorld.getChunkSource().mainThreadProcessor - .managedBlock(() -> { - // bail out early if a future fails - if (chunkLoadings.stream().anyMatch(ftr -> - ftr.isDone() && Futures.getUnchecked(ftr) == null - )) { - return false; - } - return chunkLoadings.stream().allMatch(CompletableFuture::isDone); - }); - - Map chunks = new HashMap<>(); - for (CompletableFuture future : chunkLoadings) { - @Nullable - ChunkAccess chunk = future.getNow(null); - checkState(chunk != null, "Failed to generate a chunk, regen failed."); - chunks.put(chunk.getPos(), chunk); - } - - for (BlockVector3 vec : region) { - BlockPos pos = FabricAdapter.toBlockPos(vec); - ChunkAccess chunk = chunks.get(new ChunkPos(pos)); - BlockStateHolder state = FabricAdapter.adapt(chunk.getBlockState(pos)); - BlockEntity blockEntity = chunk.getBlockEntity(pos); - if (blockEntity != null) { - var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); - blockEntity.saveWithId(tagValueOutput); - net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); - state = state.toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(tag))); + ServerWorld originalWorld = (ServerWorld) getWorld(); + + MinecraftServer server = originalWorld.getServer(); + WorldSaveHandler saveHandler = new WorldSaveHandler(saveFolder, originalWorld.getSaveHandler().getWorldDir().getName(), server, server.getDataFixer()); + World freshWorld = new ServerWorld(server, server.getWorkerExecutor(), saveHandler, originalWorld.getLevelProperties(), + originalWorld.dimension.getType(), originalWorld.getProfiler(), new NoOpChunkStatusListener()); + + // Pre-gen all the chunks + // We need to also pull one more chunk in every direction + CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 0, 16), region.getMaximumPoint().add(16, 0, 16)); + for (BlockVector2 chunk : expandedPreGen.getChunks()) { + freshWorld.getChunk(chunk.getBlockX(), chunk.getBlockZ()); } - extent.setBlock(vec, state.toBaseBlock()); - if (options.shouldRegenBiomes()) { - BiomeType biome = getBiomeInChunk(vec, chunk); - extent.setBiome(vec, biome); + FabricWorld from = new FabricWorld(freshWorld); + for (BlockVector3 vec : region) { + editSession.setBlock(vec, from.getFullBlock(vec)); } + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } finally { + saveFolder.delete(); } - } - private List> submitChunkLoadTasks(Region region, ServerLevel world) { - List> chunkLoadings = new ArrayList<>(); - // Pre-gen all the chunks - for (BlockVector2 chunk : region.getChunks()) { - chunkLoadings.add( - world.getChunkSource().getChunkFuture(chunk.x(), chunk.z(), ChunkStatus.FEATURES, true) - .thenApply(either -> either.orElse(null)) - ); - } - return chunkLoadings; + return true; } - @SuppressWarnings("deprecation") @Nullable - private static ResourceKey createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType type) { - return switch (type) { - // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields - case TREE -> TreePlacements.OAK_CHECKED; - case BIG_TREE -> TreePlacements.FANCY_OAK_CHECKED; - case REDWOOD -> TreePlacements.SPRUCE_CHECKED; - case TALL_REDWOOD -> TreePlacements.MEGA_SPRUCE_CHECKED; - case MEGA_REDWOOD -> TreePlacements.MEGA_PINE_CHECKED; - case BIRCH -> TreePlacements.BIRCH_CHECKED; - case JUNGLE -> TreePlacements.MEGA_JUNGLE_TREE_CHECKED; - case SMALL_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; - case SHORT_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; - case JUNGLE_BUSH -> TreePlacements.JUNGLE_BUSH; - case SWAMP -> TreePlacements.OAK_CHECKED; - case ACACIA -> TreePlacements.ACACIA_CHECKED; - case DARK_OAK -> TreePlacements.DARK_OAK_CHECKED; - case TALL_BIRCH -> TreePlacements.SUPER_BIRCH_BEES_0002; - case WARPED_FUNGUS -> TreePlacements.WARPED_FUNGI; - case CRIMSON_FUNGUS -> TreePlacements.CRIMSON_FUNGI; - case CHORUS_PLANT -> EndPlacements.CHORUS_PLANT; - case MANGROVE -> TreePlacements.MANGROVE_CHECKED; - case TALL_MANGROVE -> TreePlacements.TALL_MANGROVE_CHECKED; - case CHERRY -> TreePlacements.CHERRY_CHECKED; - case PALE_OAK -> TreePlacements.PALE_OAK_CHECKED; - case PALE_OAK_CREAKING -> TreePlacements.PALE_OAK_CREAKING_CHECKED; - case RANDOM -> createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(com.sk89q.worldedit.util.TreeGenerator.TreeType.values().length)]); - default -> null; - }; - } - - @SuppressWarnings("deprecation") - @Override - public boolean generateTree(com.sk89q.worldedit.util.TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { - ServerLevel world = (ServerLevel) getWorld(); - PlacedFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) - .map(k -> world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(k)) - .orElse(null); - ServerChunkCache chunkManager = world.getChunkSource(); - if (type == com.sk89q.worldedit.util.TreeGenerator.TreeType.CHORUS_PLANT) { - position = position.add(0, 1, 0); - } - try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { - return generator != null && generator.place( - proxyLevel.level(), chunkManager.getGenerator(), random, - FabricAdapter.toBlockPos(position) - ); + private static Feature createTreeFeatureGenerator(TreeType type) { + switch (type) { + case TREE: return new OakTreeFeature(DefaultFeatureConfig::deserialize, true); + case BIG_TREE: return new LargeOakTreeFeature(DefaultFeatureConfig::deserialize, true); + case REDWOOD: return new PineTreeFeature(DefaultFeatureConfig::deserialize); + case TALL_REDWOOD: return new SpruceTreeFeature(DefaultFeatureConfig::deserialize, true); + case BIRCH: return new BirchTreeFeature(DefaultFeatureConfig::deserialize, true, false); + case JUNGLE: return new MegaJungleTreeFeature(DefaultFeatureConfig::deserialize, true, 10, 20, JUNGLE_LOG, JUNGLE_LEAF); + case SMALL_JUNGLE: return new JungleTreeFeature(DefaultFeatureConfig::deserialize, true, 4 + random.nextInt(7), JUNGLE_LOG, JUNGLE_LEAF, false); + case SHORT_JUNGLE: return new JungleTreeFeature(DefaultFeatureConfig::deserialize, true, 4 + random.nextInt(7), JUNGLE_LOG, JUNGLE_LEAF, true); + case JUNGLE_BUSH: return new JungleGroundBushFeature(DefaultFeatureConfig::deserialize, JUNGLE_LOG, JUNGLE_SHRUB); + case SWAMP: return new SwampTreeFeature(DefaultFeatureConfig::deserialize); + case ACACIA: return new SavannaTreeFeature(DefaultFeatureConfig::deserialize, true); + case DARK_OAK: return new DarkOakTreeFeature(DefaultFeatureConfig::deserialize, true); + case MEGA_REDWOOD: return new MegaPineTreeFeature(DefaultFeatureConfig::deserialize, true, random.nextBoolean()); + case TALL_BIRCH: return new BirchTreeFeature(DefaultFeatureConfig::deserialize, true, true); + case RED_MUSHROOM: return new HugeRedMushroomFeature(PlantedFeatureConfig::deserialize); + case BROWN_MUSHROOM: return new HugeBrownMushroomFeature(PlantedFeatureConfig::deserialize); + case RANDOM: return createTreeFeatureGenerator(TreeType.values()[ThreadLocalRandom.current().nextInt(TreeType.values().length)]); + default: + return null; + } + } + + private FeatureConfig createFeatureConfig(TreeType type) { + if (type == TreeType.RED_MUSHROOM || type == TreeType.BROWN_MUSHROOM) { + return new PlantedFeatureConfig(true); + } else { + return new DefaultFeatureConfig(); } } @Override public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { - ServerLevel world = (ServerLevel) getWorld(); - PlacedFeature generator = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); - ServerChunkCache chunkManager = world.getChunkSource(); - try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { - return generator != null && generator.place( - proxyLevel.level(), chunkManager.getGenerator(), random, - FabricAdapter.toBlockPos(position) - ); - } - } - - @Override - public boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { - ServerLevel world = (ServerLevel) getWorld(); - ConfiguredFeature feature = world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); - ServerChunkCache chunkManager = world.getChunkSource(); - try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { - return feature != null && feature.place( - proxyLevel.level(), chunkManager.getGenerator(), random, - FabricAdapter.toBlockPos(position) - ); - } catch (MaxChangedBlocksException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { - ServerLevel world = (ServerLevel) getWorld(); - Registry structureRegistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE); - Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id())); - if (structure == null) { - return false; - } - - ServerChunkCache chunkManager = world.getChunkSource(); - try (FabricServerLevelDelegateProxy.LevelAndProxy proxyLevel = FabricServerLevelDelegateProxy.newInstance(editSession, world)) { - ChunkPos chunkPos = new ChunkPos(new BlockPos(position.x(), position.y(), position.z())); - StructureStart structureStart = structure.generate( - structureRegistry.wrapAsHolder(structure), world.dimension(), world.registryAccess(), - chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), - world.getStructureManager(), world.getSeed(), chunkPos, 0, proxyLevel.level(), - biome -> true - ); - - if (!structureStart.isValid()) { - return false; - } else { - BoundingBox boundingBox = structureStart.getBoundingBox(); - ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ())); - ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ())); - ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> - structureStart.placeInChunk( - proxyLevel.level(), world.structureManager(), chunkManager.getGenerator(), world.getRandom(), - new BoundingBox(chunkPosx.getMinBlockX(), world.getMinY(), chunkPosx.getMinBlockZ(), - chunkPosx.getMaxBlockX(), world.getMaxY(), chunkPosx.getMaxBlockZ()), - chunkPosx - ) - ); - return true; - } - } catch (MaxChangedBlocksException e) { - throw new RuntimeException(e); - } + @SuppressWarnings("unchecked") + Feature generator = (Feature) createTreeFeatureGenerator(type); + return generator != null + && generator.generate(getWorld(), getWorld().getChunkManager().getChunkGenerator(), random, + FabricAdapter.toBlockPos(position), createFeatureConfig(type)); } @Override @@ -572,27 +386,22 @@ public void fixAfterFastMode(Iterable chunks) { } @Override - public void sendBiomeUpdates(Iterable chunks) { - List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); + public void fixLighting(Iterable chunks) { + World world = getWorld(); for (BlockVector2 chunk : chunks) { - nativeChunks.add(getWorld().getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); + world.getChunkManager().getLightingProvider().suppressLight(new ChunkPos(chunk.getBlockX(), chunk.getBlockZ()), true); } - ((ServerLevel) getWorld()).getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } @Override - public void fixLighting(Iterable chunks) { - Level world = getWorld(); - for (BlockVector2 chunk : chunks) { - world.getChunkSource().getLightEngine().setLightEnabled( - new ChunkPos(chunk.x(), chunk.z()), true - ); - } + public boolean playEffect(Vector3 position, int type, int data) { + getWorld().playLevelEvent(type, FabricAdapter.toBlockPos(position.toBlockPoint()), data); + return true; } @Override public WeatherType getWeather() { - LevelData info = getWorld().getLevelData(); + LevelProperties info = getWorld().getLevelProperties(); if (info.isThundering()) { return WeatherTypes.THUNDER_STORM; } @@ -604,7 +413,7 @@ public WeatherType getWeather() { @Override public long getRemainingWeatherDuration() { - ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); + LevelProperties info = getWorld().getLevelProperties(); if (info.isThundering()) { return info.getThunderTime(); } @@ -621,7 +430,7 @@ public void setWeather(WeatherType weatherType) { @Override public void setWeather(WeatherType weatherType, long duration) { - ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); + LevelProperties info = getWorld().getLevelProperties(); if (weatherType == WeatherTypes.THUNDER_STORM) { info.setClearWeatherTime(0); info.setThundering(true); @@ -637,41 +446,40 @@ public void setWeather(WeatherType weatherType, long duration) { } } - @Override - public int getMinY() { - return getWorld().getMinY(); - } - @Override public int getMaxY() { - return getWorld().getMaxY(); + return getWorld().getHeight() - 1; } @Override public BlockVector3 getSpawnPosition() { - return FabricAdapter.adapt(getWorld().getLevelData().getRespawnData().pos()); + return FabricAdapter.adapt(getWorld().getSpawnPos()); } @Override public BlockState getBlock(BlockVector3 position) { - net.minecraft.world.level.block.state.BlockState mcState = getWorld() - .getChunk(position.x() >> 4, position.z() >> 4) + net.minecraft.block.BlockState mcState = getWorld() + .getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4) .getBlockState(FabricAdapter.toBlockPos(position)); + BlockState matchingBlock = BlockStateIdAccess.getBlockStateById(Block.getRawIdFromState(mcState)); + if (matchingBlock != null) { + return matchingBlock; + } + return FabricAdapter.adapt(mcState); } @Override public BaseBlock getFullBlock(BlockVector3 position) { - BlockPos pos = new BlockPos(position.x(), position.y(), position.z()); + BlockPos pos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()); // Avoid creation by using the CHECK mode -- if it's needed, it'll be re-created anyways - BlockEntity tile = ((LevelChunk) getWorld().getChunk(pos)).getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + BlockEntity tile = ((WorldChunk) getWorld().getChunk(pos)).getBlockEntity(pos, WorldChunk.CreationType.CHECK); if (tile != null) { - var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); - tile.saveWithId(tagValueOutput); - net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); - return getBlock(position).toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(tag))); + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + tile.toTag(tag); + return getBlock(position).toBaseBlock(NBTConverter.fromNative(tag)); } else { return getBlock(position).toBaseBlock(); } @@ -684,9 +492,10 @@ public int hashCode() { @Override public boolean equals(Object o) { - if ((o instanceof FabricWorld other)) { - Level otherWorld = other.worldRef.get(); - Level thisWorld = worldRef.get(); + if ((o instanceof FabricWorld)) { + FabricWorld other = ((FabricWorld) o); + World otherWorld = other.worldRef.get(); + World thisWorld = worldRef.get(); return otherWorld != null && otherWorld.equals(thisWorld); } else if (o instanceof com.sk89q.worldedit.world.World) { return ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); @@ -697,87 +506,75 @@ public boolean equals(Object o) { @Override public List getEntities(Region region) { - final Level world = getWorld(); - AABB box = new AABB( - FabricAdapter.toVec3(region.getMinimumPoint()), - FabricAdapter.toVec3(region.getMaximumPoint().add(BlockVector3.ONE)) - ); - List nmsEntities = world.getEntities( - (net.minecraft.world.entity.Entity) null, - box, - e -> region.contains(FabricAdapter.adapt(e.blockPosition())) - ); - return nmsEntities.stream() - .map(FabricEntity::new) - .collect(ImmutableList.toImmutableList()); + final World world = getWorld(); + if (!(world instanceof ServerWorld)) { + return Collections.emptyList(); + } + return ((ServerWorld) world).getEntities(null, entity -> true) + .stream() + .filter(e -> region.contains(FabricAdapter.adapt(e.getBlockPos()))) + .map(FabricEntity::new).collect(Collectors.toList()); } @Override public List getEntities() { - final Level world = getWorld(); - if (!(world instanceof ServerLevel)) { + final World world = getWorld(); + if (!(world instanceof ServerWorld)) { return Collections.emptyList(); } - return Streams.stream(((ServerLevel) world).getAllEntities()) - .map(FabricEntity::new) - .collect(ImmutableList.toImmutableList()); + return ((ServerWorld) world).getEntities(null, entity -> true) + .stream() + .map(FabricEntity::new) + .collect(Collectors.toList()); } @Nullable @Override public Entity createEntity(Location location, BaseEntity entity) { - ServerLevel world = (ServerLevel) getWorld(); - String entityId = entity.getType().id(); - final Optional> entityType = EntityType.byString(entityId); - if (entityType.isEmpty()) { - return null; - } - LinCompoundTag linTag = entity.getNbt(); - net.minecraft.nbt.CompoundTag tag; - if (linTag != null) { - tag = NBTConverter.toNative(linTag); - removeUnwantedEntityTagsRecursively(tag); - } else { - tag = new net.minecraft.nbt.CompoundTag(); - } - tag.putString("id", entityId); - - net.minecraft.world.entity.Entity createdEntity = EntityType.loadEntityRecursive(tag, world, EntitySpawnReason.COMMAND, (loadedEntity) -> { - loadedEntity.absSnapTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - return loadedEntity; - }); + World world = getWorld(); + final Optional> entityType = EntityType.get(entity.getType().getId()); + if (!entityType.isPresent()) return null; + net.minecraft.entity.Entity createdEntity = entityType.get().create(world); if (createdEntity != null) { - world.addFreshEntityWithPassengers(createdEntity); + CompoundTag nativeTag = entity.getNbtData(); + if (nativeTag != null) { + net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(entity.getNbtData()); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + createdEntity.fromTag(tag); + } + + createdEntity.setPositionAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + world.spawnEntity(createdEntity); return new FabricEntity(createdEntity); + } else { + return null; } - return null; } - private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { - for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { - tag.remove(name); + /** + * Thrown when the reference to the world is lost. + */ + @SuppressWarnings("serial") + private static final class WorldReferenceLostException extends WorldEditException { + private WorldReferenceLostException(String message) { + super(message); } - - // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive - tag.getList("Passengers").ifPresent(nbttaglist -> { - for (int i = 0; i < nbttaglist.size(); ++i) { - removeUnwantedEntityTagsRecursively(nbttaglist.getCompoundOrEmpty(i)); - } - }); } - @Override - public Mask createLiquidMask() { - return new AbstractExtentMask(this) { - @Override - public boolean test(BlockVector3 vector) { - return FabricAdapter.adapt(getExtent().getBlock(vector)).getBlock() instanceof LiquidBlock; - } - }; - } + private static class NoOpChunkStatusListener implements WorldGenerationProgressListener { + @Override + public void start(ChunkPos chunkPos) { + } - @Override - public boolean isValid() { - return worldRef.get() != null; + @Override + public void setChunkStatus(ChunkPos chunkPos, @Nullable ChunkStatus chunkStatus) { + } + + @Override + public void stop() { + } } } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java index 29a1021898..755ee118e2 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java @@ -22,34 +22,22 @@ import com.mojang.brigadier.CommandDispatcher; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.command.util.PermissionCondition; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; -import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; -import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler; import com.sk89q.worldedit.internal.anvil.ChunkDeleter; -import com.sk89q.worldedit.internal.event.InteractionDebouncer; -import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.lifecycle.Lifecycled; -import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled; -import com.sk89q.worldedit.world.biome.BiomeCategory; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockCategory; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.entity.EntityType; -import com.sk89q.worldedit.world.gamemode.GameModes; -import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; -import com.sk89q.worldedit.world.generation.StructureType; -import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; -import com.sk89q.worldedit.world.weather.WeatherTypes; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.AttackBlockCallback; @@ -58,48 +46,30 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.Version; -import net.fabricmc.loader.api.metadata.ModMetadata; -import net.fabricmc.loader.api.metadata.version.VersionPredicate; -import net.minecraft.commands.CommandBuildContext; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.Commands; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; -import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; -import net.minecraft.world.level.levelgen.feature.TreeFeature; -import net.minecraft.world.level.levelgen.placement.PlacedFeature; -import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.tag.BlockTags; +import net.minecraft.tag.ItemTags; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.enginehub.piston.Command; -import org.enginehub.worldeditcui.protocol.CUIPacket; -import org.enginehub.worldeditcui.protocol.CUIPacketHandler; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; import static com.sk89q.worldedit.fabric.FabricAdapter.adaptPlayer; @@ -110,39 +80,14 @@ */ public class FabricWorldEdit implements ModInitializer { - private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static final Logger LOGGER = LogManager.getLogger(); public static final String MOD_ID = "worldedit"; - - public static final Lifecycled LIFECYCLED_SERVER; - - static { - SimpleLifecycled lifecycledServer = SimpleLifecycled.invalid(); - ServerLifecycleEvents.SERVER_STARTED.register(lifecycledServer::newValue); - ServerLifecycleEvents.SERVER_STOPPING.register(__ -> lifecycledServer.invalidate()); - LIFECYCLED_SERVER = lifecycledServer; - } - - /** - * {@return current server's registry access} Not for long-term storage. - */ - public static RegistryAccess registryAccess() { - return LIFECYCLED_SERVER.valueOrThrow().registryAccess(); - } - - /** - * {@return current server's registry} Not for long-term storage. - * - * @param key the registry key - */ - public static Registry getRegistry(ResourceKey> key) { - return LIFECYCLED_SERVER.valueOrThrow().registryAccess().lookupOrThrow(key); - } + public static final String CUI_PLUGIN_CHANNEL = "cui"; private FabricPermissionsProvider provider; public static FabricWorldEdit inst; - private InteractionDebouncer debouncer; private FabricPlatform platform; private FabricConfiguration config; private Path workingDir; @@ -168,15 +113,8 @@ public void onInitialize() { throw new UncheckedIOException(e); } } - this.platform = new FabricPlatform(this); - debouncer = new InteractionDebouncer(platform); - WorldEdit.getInstance().getPlatformManager().register(platform); - - config = new FabricConfiguration(this); - this.provider = getInitialPermissionsProvider(); - - CUIPacketHandler.instance().registerServerboundHandler(this::onCuiPacket); + WECUIPacketHandler.init(); ServerTickEvents.END_SERVER_TICK.register(ThreadSafeCache.getInstance()); CommandRegistrationCallback.EVENT.register(this::registerCommands); @@ -186,143 +124,83 @@ public void onInitialize() { ServerPlayConnectionEvents.DISCONNECT.register(this::onPlayerDisconnect); AttackBlockCallback.EVENT.register(this::onLeftClickBlock); UseBlockCallback.EVENT.register(this::onRightClickBlock); - UseItemCallback.EVENT.register(this::onRightClickItem); + UseItemCallback.EVENT.register(this::onRightClickAir); LOGGER.info("WorldEdit for Fabric (version " + getInternalVersion() + ") is loaded"); } - private void registerCommands(CommandDispatcher dispatcher, CommandBuildContext registryAccess, Commands.CommandSelection environment) { - WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); + private void registerCommands(CommandDispatcher dispatcher, boolean dedicated) { PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); + if (manager.getPlatforms().isEmpty()) { + // We'll register as part of our platform initialization later. + return; + } + + // This is a re-register (due to /reload), we must add our commands now + Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); if (commandsPlatform != platform || !platform.isHookingEvents()) { - // We're not in control of commands/events -- do not register. + // We're not in control of commands/events -- do not re-register. return; } + platform.setNativeDispatcher(dispatcher); + platform.registerCommands(manager.getPlatformCommandManager().getCommandManager()); + } - List commands = manager.getPlatformCommandManager().getCommandManager() - .getAllCommands().toList(); - for (Command command : commands) { - CommandWrapper.register(dispatcher, command); - Set perms = command.getCondition().as(PermissionCondition.class) - .map(PermissionCondition::getPermissions) - .orElseGet(Collections::emptySet); - if (!perms.isEmpty()) { - perms.forEach(getPermissionsProvider()::registerPermission); - } - } + private void setupPlatform(MinecraftServer server) { + this.platform = new FabricPlatform(this, server); + + WorldEdit.getInstance().getPlatformManager().register(platform); + + this.provider = getInitialPermissionsProvider(); } private FabricPermissionsProvider getInitialPermissionsProvider() { try { Class.forName("me.lucko.fabric.api.permissions.v0.Permissions", false, getClass().getClassLoader()); - Optional version = FabricLoader.getInstance().getModContainer("fabric-permissions-api-v0") - .map(ModContainer::getMetadata) - .map(ModMetadata::getVersion); - - if (version.isPresent() && !VersionPredicate.parse(">=0.5.0").test(version.get())) { - throw new RuntimeException("Fabric permissions version " + version.get() + " is not supported. Please update Fabric Permissions API"); - } - return new FabricPermissionsProvider.LuckoFabricPermissionsProvider(platform); } catch (ClassNotFoundException ignored) { // fallback to vanilla - } catch (Exception e) { - // catch any exception to prevent crashing the server, but still print a warning - LOGGER.warn("Failed to load Fabric permissions provider. Falling back to Minecraft", e); } - return new FabricPermissionsProvider.VanillaPermissionsProvider(platform); } private void setupRegistries(MinecraftServer server) { // Blocks - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.BLOCK).keySet()) { - String key = name.toString(); - if (BlockType.REGISTRY.get(key) == null) { - BlockType.REGISTRY.register(key, new BlockType(key, - input -> FabricAdapter.adapt(FabricAdapter.adapt(input.getBlockType()).defaultBlockState()))); + for (Identifier name : Registry.BLOCK.getIds()) { + if (BlockType.REGISTRY.get(name.toString()) == null) { + BlockType.REGISTRY.register(name.toString(), new BlockType(name.toString(), + input -> FabricAdapter.adapt(FabricAdapter.adapt(input.getBlockType()).getDefaultState()))); } } // Items - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.ITEM).keySet()) { - String key = name.toString(); - if (ItemType.REGISTRY.get(key) == null) { - ItemType.REGISTRY.register(key, new ItemType(key)); + for (Identifier name : Registry.ITEM.getIds()) { + if (ItemType.REGISTRY.get(name.toString()) == null) { + ItemType.REGISTRY.register(name.toString(), new ItemType(name.toString())); } } // Entities - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.ENTITY_TYPE).keySet()) { - String key = name.toString(); - if (EntityType.REGISTRY.get(key) == null) { - EntityType.REGISTRY.register(key, new EntityType(key)); + for (Identifier name : Registry.ENTITY_TYPE.getIds()) { + if (EntityType.REGISTRY.get(name.toString()) == null) { + EntityType.REGISTRY.register(name.toString(), new EntityType(name.toString())); } } // Biomes - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { - String key = name.toString(); - if (BiomeType.REGISTRY.get(key) == null) { - BiomeType.REGISTRY.register(key, new BiomeType(key)); + for (Identifier name : server.getRegistryManager().get(Registry.BIOME_KEY).getIds()) { + if (BiomeType.REGISTRY.get(name.toString()) == null) { + BiomeType.REGISTRY.register(name.toString(), new BiomeType(name.toString())); } } // Tags - server.registryAccess().lookupOrThrow(Registries.BLOCK).getTags().map(t -> t.key().location()).forEach(name -> { - String key = name.toString(); - if (BlockCategory.REGISTRY.get(key) == null) { - BlockCategory.REGISTRY.register(key, new BlockCategory(key)); - } - }); - server.registryAccess().lookupOrThrow(Registries.ITEM).getTags().map(t -> t.key().location()).forEach(name -> { - String key = name.toString(); - if (ItemCategory.REGISTRY.get(key) == null) { - ItemCategory.REGISTRY.register(key, new ItemCategory(key)); - } - }); - Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); - biomeRegistry.getTags().forEach(tag -> { - String key = tag.key().location().toString(); - if (BiomeCategory.REGISTRY.get(key) == null) { - BiomeCategory.REGISTRY.register(key, new BiomeCategory( - key, - () -> biomeRegistry.get(tag.key()) - .stream() - .flatMap(HolderSet.Named::stream) - .map(Holder::value) - .map(FabricAdapter::adapt) - .collect(Collectors.toSet())) - ); - } - }); - // Features - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { - String key = name.toString(); - if (ConfiguredFeatureType.REGISTRY.get(key) == null) { - ConfiguredFeatureType.REGISTRY.register(key, new ConfiguredFeatureType(key)); - } - } - // Structures - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { - String key = name.toString(); - if (StructureType.REGISTRY.get(key) == null) { - StructureType.REGISTRY.register(key, new StructureType(key)); + for (Identifier name : BlockTags.getTagGroup().getTagIds()) { + if (BlockCategory.REGISTRY.get(name.toString()) == null) { + BlockCategory.REGISTRY.register(name.toString(), new BlockCategory(name.toString())); } } - // Trees - Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); - for (ResourceLocation name : placedFeatureRegistry.keySet()) { - // Do some hackery to make sure this is a tree - var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); - if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { - String key = name.toString(); - if (TreeType.REGISTRY.get(key) == null) { - TreeType.REGISTRY.register(key, new TreeType(key)); - } + for (Identifier name : ItemTags.getTagGroup().getTagIds()) { + if (ItemCategory.REGISTRY.get(name.toString()) == null) { + ItemCategory.REGISTRY.register(name.toString(), new ItemCategory(name.toString())); } } - - // ... :| - GameModes.get(""); - WeatherTypes.get(""); - com.sk89q.worldedit.registry.Registries.get(""); } private void onStartingServer(MinecraftServer minecraftServer) { @@ -333,120 +211,105 @@ private void onStartingServer(MinecraftServer minecraftServer) { } private void onStartServer(MinecraftServer minecraftServer) { + FabricAdapter.setServer(minecraftServer); + setupPlatform(minecraftServer); setupRegistries(minecraftServer); + config = new FabricConfiguration(this); config.load(); - WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); + WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); + minecraftServer.reloadResources( + minecraftServer.getDataPackManager().getEnabledNames() + ); } private void onStopServer(MinecraftServer minecraftServer) { WorldEdit worldEdit = WorldEdit.getInstance(); worldEdit.getSessionManager().unload(); - WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); + worldEdit.getPlatformManager().unregister(platform); } - private boolean skipEvents() { - return platform == null || !platform.isHookingEvents(); - } + private boolean shouldSkip() { + if (platform == null) { + return true; + } - private boolean skipInteractionEvent(Player player, InteractionHand hand) { - return skipEvents() || hand != InteractionHand.MAIN_HAND || player.level().isClientSide() || !(player instanceof ServerPlayer); + return !platform.isHookingEvents(); // We have to be told to catch these events } - private InteractionResult onLeftClickBlock(Player playerEntity, Level world, InteractionHand hand, BlockPos blockPos, Direction direction) { - if (skipInteractionEvent(playerEntity, hand)) { - return InteractionResult.PASS; + private ActionResult onLeftClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockPos blockPos, Direction direction) { + if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { + return ActionResult.PASS; } WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity); + FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); FabricWorld localWorld = getWorld(world); Location pos = new Location(localWorld, - blockPos.getX(), - blockPos.getY(), - blockPos.getZ() + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() ); com.sk89q.worldedit.util.Direction weDirection = FabricAdapter.adaptEnumFacing(direction); - boolean result = we.handleBlockLeftClick(player, pos, weDirection) || we.handleArmSwing(player); - debouncer.setLastInteraction(player, result); + if (we.handleBlockLeftClick(player, pos, weDirection)) { + return ActionResult.SUCCESS; + } - return result ? InteractionResult.SUCCESS : InteractionResult.PASS; + if (we.handleArmSwing(player)) { + return ActionResult.SUCCESS; + } + + return ActionResult.PASS; } - private InteractionResult onRightClickBlock(Player playerEntity, Level world, InteractionHand hand, BlockHitResult blockHitResult) { - if (skipInteractionEvent(playerEntity, hand)) { - return InteractionResult.PASS; + private ActionResult onRightClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { + if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { + return ActionResult.PASS; } WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity); + FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); FabricWorld localWorld = getWorld(world); Location pos = new Location(localWorld, - blockHitResult.getBlockPos().getX(), - blockHitResult.getBlockPos().getY(), - blockHitResult.getBlockPos().getZ() + blockHitResult.getBlockPos().getX(), + blockHitResult.getBlockPos().getY(), + blockHitResult.getBlockPos().getZ() ); - com.sk89q.worldedit.util.Direction direction = FabricAdapter.adaptEnumFacing(blockHitResult.getDirection()); - - boolean result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player); - debouncer.setLastInteraction(player, result); - - return result ? InteractionResult.SUCCESS : InteractionResult.PASS; - } + com.sk89q.worldedit.util.Direction direction = FabricAdapter.adaptEnumFacing(blockHitResult.getSide()); - public void onLeftClickAir(ServerPlayer playerEntity, InteractionHand hand) { - if (skipInteractionEvent(playerEntity, hand)) { - return; + if (we.handleBlockRightClick(player, pos, direction)) { + return ActionResult.SUCCESS; } - WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer(playerEntity); - - Optional previousResult = debouncer.getDuplicateInteractionResult(player); - if (previousResult.isPresent()) { - return; + if (we.handleRightClick(player)) { + return ActionResult.SUCCESS; } - boolean result = we.handleArmSwing(player); - debouncer.setLastInteraction(player, result); + return ActionResult.PASS; } - private InteractionResult onRightClickItem(Player playerEntity, Level world, InteractionHand hand) { - if (skipInteractionEvent(playerEntity, hand)) { - return InteractionResult.PASS; + private TypedActionResult onRightClickAir(PlayerEntity playerEntity, World world, Hand hand) { + ItemStack stackInHand = playerEntity.getStackInHand(hand); + if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { + return TypedActionResult.pass(stackInHand); } WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity); + FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); - Optional previousResult = debouncer.getDuplicateInteractionResult(player); - if (previousResult.isPresent()) { - return previousResult.get() ? InteractionResult.SUCCESS : InteractionResult.PASS; + if (we.handleRightClick(player)) { + return TypedActionResult.success(stackInHand); } - boolean result = we.handleRightClick(player); - debouncer.setLastInteraction(player, result); - - return result ? InteractionResult.SUCCESS : InteractionResult.PASS; + return TypedActionResult.pass(stackInHand); } - private void onPlayerDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server) { - debouncer.clearInteraction(adaptPlayer(handler.player)); + // TODO Pass empty left click to server + private void onPlayerDisconnect(ServerPlayNetworkHandler handler, MinecraftServer server) { WorldEdit.getInstance().getEventBus() - .post(new SessionIdleEvent(new FabricPlayer.SessionKeyImpl(handler.player))); - } - - private void onCuiPacket(CUIPacket payload, CUIPacketHandler.PacketContext context) { - if (!(context.player() instanceof ServerPlayer player)) { - // Ignore - this is not a server-bound packet - return; - } - - FabricPlayer actor = FabricAdapter.adaptPlayer(player); - LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor); - session.handleCUIInitializationMessage(payload.eventType(), payload.args(), actor); + .post(new SessionIdleEvent(new FabricPlayer.SessionKeyImpl(handler.player))); } /** @@ -464,7 +327,7 @@ FabricConfiguration getConfig() { * @param player the player * @return the session */ - public LocalSession getSession(ServerPlayer player) { + public LocalSession getSession(ServerPlayerEntity player) { checkNotNull(player); return WorldEdit.getInstance().getSessionManager().get(adaptPlayer(player)); } @@ -475,7 +338,7 @@ public LocalSession getSession(ServerPlayer player) { * @param world the world * @return the WorldEdit world */ - public FabricWorld getWorld(Level world) { + public FabricWorld getWorld(World world) { checkNotNull(world); return new FabricWorld(world); } diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java deleted file mode 100644 index 632352a53d..0000000000 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java +++ /dev/null @@ -1,765 +0,0 @@ -/* - * 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.neoforge; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.collect.Streams; -import com.google.common.util.concurrent.Futures; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.MaxChangedBlocksException; -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.blocks.BaseItem; -import com.sk89q.worldedit.blocks.BaseItemStack; -import com.sk89q.worldedit.entity.BaseEntity; -import com.sk89q.worldedit.entity.Entity; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.function.mask.AbstractExtentMask; -import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.internal.Constants; -import com.sk89q.worldedit.math.BlockVector2; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector3; -import com.sk89q.worldedit.neoforge.internal.NBTConverter; -import com.sk89q.worldedit.neoforge.internal.NeoForgeEntity; -import com.sk89q.worldedit.neoforge.internal.NeoForgeServerLevelDelegateProxy; -import com.sk89q.worldedit.neoforge.internal.NeoForgeWorldNativeAccess; -import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.util.Direction; -import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.concurrency.LazyReference; -import com.sk89q.worldedit.util.io.file.SafeFiles; -import com.sk89q.worldedit.world.AbstractWorld; -import com.sk89q.worldedit.world.RegenOptions; -import com.sk89q.worldedit.world.biome.BiomeType; -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.generation.ConfiguredFeatureType; -import com.sk89q.worldedit.world.generation.StructureType; -import com.sk89q.worldedit.world.item.ItemTypes; -import com.sk89q.worldedit.world.weather.WeatherType; -import com.sk89q.worldedit.world.weather.WeatherTypes; -import net.minecraft.Util; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.SectionPos; -import net.minecraft.core.registries.Registries; -import net.minecraft.data.worldgen.placement.EndPlacements; -import net.minecraft.data.worldgen.placement.TreePlacements; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.ProblemReporter; -import net.minecraft.util.RandomSource; -import net.minecraft.util.thread.BlockableEventLoop; -import net.minecraft.world.Clearable; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.EntitySpawnReason; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.UseOnContext; -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.LiquidBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.chunk.PalettedContainer; -import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.minecraft.world.level.dimension.LevelStem; -import net.minecraft.world.level.levelgen.WorldOptions; -import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; -import net.minecraft.world.level.levelgen.placement.PlacedFeature; -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.LevelData; -import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.PrimaryLevelData; -import net.minecraft.world.level.storage.ServerLevelData; -import net.minecraft.world.level.storage.TagValueOutput; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.BlockHitResult; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ThreadLocalRandom; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -/** - * An adapter to Minecraft worlds for WorldEdit. - */ -public class NeoForgeWorld extends AbstractWorld { - - private static final RandomSource random = RandomSource.create(); - - private static ResourceLocation getDimensionRegistryKey(ServerLevel world) { - return Objects.requireNonNull(world.getServer(), "server cannot be null") - .registryAccess() - .lookupOrThrow(Registries.DIMENSION_TYPE) - .getKey(world.dimensionType()); - } - - private final WeakReference worldRef; - private final NeoForgeWorldNativeAccess nativeAccess; - - /** - * Construct a new world. - * - * @param world the world - */ - NeoForgeWorld(ServerLevel world) { - checkNotNull(world); - this.worldRef = new WeakReference<>(world); - this.nativeAccess = new NeoForgeWorldNativeAccess(worldRef); - } - - /** - * Get the underlying handle to the world. - * - * @return the world - * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded) - */ - public ServerLevel getWorld() { - ServerLevel world = worldRef.get(); - if (world != null) { - return world; - } else { - throw new RuntimeException("The reference to the world was lost (i.e. the world may have been unloaded)"); - } - } - - @Override - public String getName() { - return ((ServerLevelData) getWorld().getLevelData()).getLevelName(); - } - - @Override - public String id() { - return getName() + "_" + getDimensionRegistryKey(getWorld()); - } - - @Override - public Path getStoragePath() { - final ServerLevel world = getWorld(); - return world.getServer().storageSource.getDimensionPath(world.dimension()); - } - - @Override - public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { - clearContainerBlockContents(position); - return nativeAccess.setBlock(position, block, sideEffects); - } - - @Override - public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - nativeAccess.applySideEffects(position, previousType, sideEffectSet); - return Sets.intersection(NeoForgeWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); - } - - @Override - public int getBlockLightLevel(BlockVector3 position) { - checkNotNull(position); - return getWorld().getLightEmission(NeoForgeAdapter.toBlockPos(position)); - } - - @Override - public boolean clearContainerBlockContents(BlockVector3 position) { - checkNotNull(position); - - BlockEntity tile = getWorld().getBlockEntity(NeoForgeAdapter.toBlockPos(position)); - if (tile instanceof Clearable) { - ((Clearable) tile).clearContent(); - return true; - } - return false; - } - - @Override - public BiomeType getBiome(BlockVector3 position) { - checkNotNull(position); - - LevelChunk chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); - return getBiomeInChunk(position, chunk); - } - - private BiomeType getBiomeInChunk(BlockVector3 position, ChunkAccess chunk) { - return NeoForgeAdapter.adapt( - chunk.getNoiseBiome(position.x() >> 2, position.y() >> 2, position.z() >> 2).value() - ); - } - - @Override - public boolean setBiome(BlockVector3 position, BiomeType biome) { - checkNotNull(position); - checkNotNull(biome); - - LevelChunk chunk = getWorld().getChunk(position.x() >> 4, position.z() >> 4); - var biomes = (PalettedContainer>) chunk.getSection(chunk.getSectionIndex(position.y())).getBiomes(); - biomes.getAndSetUnchecked( - position.x() & 3, position.y() & 3, position.z() & 3, - getWorld().registryAccess().lookupOrThrow(Registries.BIOME) - .getOrThrow(ResourceKey.create(Registries.BIOME, ResourceLocation.parse(biome.id()))) - ); - chunk.markUnsaved(); - return true; - } - - private static final LoadingCache fakePlayers - = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(NeoForgeFakePlayer::new)); - - @Override - public boolean useItem(BlockVector3 position, BaseItem item, Direction face) { - ItemStack stack = NeoForgeAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtReference(), 1)); - ServerLevel world = getWorld(); - final NeoForgeFakePlayer fakePlayer; - try { - fakePlayer = fakePlayers.get(world); - } catch (ExecutionException ignored) { - return false; - } - fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); - fakePlayer.absSnapTo(position.x(), position.y(), position.z(), - (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); - final BlockPos blockPos = NeoForgeAdapter.toBlockPos(position); - final BlockHitResult rayTraceResult = new BlockHitResult(NeoForgeAdapter.toVec3(position), - NeoForgeAdapter.adapt(face), blockPos, false); - UseOnContext itemUseContext = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); - InteractionResult used = stack.onItemUseFirst(itemUseContext); - if (used != InteractionResult.SUCCESS) { - // try activating the block - InteractionResult resultType = getWorld().getBlockState(blockPos).useItemOn(stack, world, fakePlayer, InteractionHand.MAIN_HAND, rayTraceResult); - if (resultType.consumesAction()) { - used = resultType; - } else { - used = stack.getItem().use(world, fakePlayer, InteractionHand.MAIN_HAND); - } - } - return used == InteractionResult.SUCCESS; - } - - @Override - public void dropItem(Vector3 position, BaseItemStack item) { - checkNotNull(position); - checkNotNull(item); - - if (item.getType() == ItemTypes.AIR) { - return; - } - - ItemEntity entity = new ItemEntity(getWorld(), position.x(), position.y(), position.z(), NeoForgeAdapter.adapt(item)); - entity.setPickUpDelay(10); - getWorld().addFreshEntity(entity); - } - - @Override - public void simulateBlockMine(BlockVector3 position) { - BlockPos pos = NeoForgeAdapter.toBlockPos(position); - getWorld().destroyBlock(pos, true); - } - - @Override - public boolean canPlaceAt(BlockVector3 position, BlockState blockState) { - return NeoForgeAdapter.adapt(blockState).canSurvive(getWorld(), NeoForgeAdapter.toBlockPos(position)); - } - - // For unmapped regen names, see Fabric! - - @Override - public boolean regenerate(Region region, Extent extent, RegenOptions options) { - try { - doRegen(region, extent, options); - } catch (Exception e) { - throw new IllegalStateException("Regen failed", e); - } - - return true; - } - - private void doRegen(Region region, Extent extent, RegenOptions options) throws Exception { - Path tempDir = Files.createTempDirectory("WorldEditWorldGen"); - LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); - try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("WorldEditTempGen")) { - ServerLevel originalWorld = getWorld(); - PrimaryLevelData levelProperties = (PrimaryLevelData) originalWorld.getServer() - .getWorldData().overworldData(); - WorldOptions originalOpts = levelProperties.worldGenOptions(); - - long seed = options.getSeed().orElse(originalWorld.getSeed()); - - levelProperties.worldOptions = options.getSeed().isPresent() - ? originalOpts.withSeed(OptionalLong.of(seed)) - : originalOpts; - - ResourceKey worldRegKey = originalWorld.dimension(); - try (ServerLevel serverWorld = new ServerLevel( - originalWorld.getServer(), Util.backgroundExecutor(), session, - ((ServerLevelData) originalWorld.getLevelData()), - worldRegKey, - new LevelStem( - originalWorld.dimensionTypeRegistration(), - originalWorld.getChunkSource().getGenerator() - ), - originalWorld.isDebug(), - seed, - // No spawners are needed for this world. - ImmutableList.of(), - // This controls ticking, we don't need it so set it to false. - false, - originalWorld.getRandomSequences() - )) { - regenForWorld(region, extent, serverWorld, options); - - // drive the server executor until all tasks are popped off - while (originalWorld.getServer().pollTask()) { - Thread.yield(); - } - } finally { - levelProperties.worldOptions = originalOpts; - } - } finally { - SafeFiles.tryHardToDeleteDir(tempDir); - } - } - - private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, - RegenOptions options) throws WorldEditException { - List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); - - // drive executor until loading finishes - BlockableEventLoop executor = serverWorld.getChunkSource().mainThreadProcessor; - executor.managedBlock(() -> { - // bail out early if a future fails - if (chunkLoadings.stream().anyMatch(ftr -> - ftr.isDone() && Futures.getUnchecked(ftr) == null - )) { - return false; - } - return chunkLoadings.stream().allMatch(CompletableFuture::isDone); - }); - - Map chunks = new HashMap<>(); - for (CompletableFuture future : chunkLoadings) { - @Nullable - ChunkAccess chunk = future.getNow(null); - checkState(chunk != null, "Failed to generate a chunk, regen failed."); - chunks.put(chunk.getPos(), chunk); - } - - for (BlockVector3 vec : region) { - BlockPos pos = NeoForgeAdapter.toBlockPos(vec); - ChunkAccess chunk = chunks.get(new ChunkPos(pos)); - BlockStateHolder state = NeoForgeAdapter.adapt(chunk.getBlockState(pos)); - BlockEntity blockEntity = chunk.getBlockEntity(pos); - if (blockEntity != null) { - var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); - blockEntity.saveWithId(tagValueOutput); - net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); - state = state.toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(tag))); - } - extent.setBlock(vec, state.toBaseBlock()); - - if (options.shouldRegenBiomes()) { - BiomeType biome = getBiomeInChunk(vec, chunk); - extent.setBiome(vec, biome); - } - } - } - - private List> submitChunkLoadTasks(Region region, ServerLevel world) { - List> chunkLoadings = new ArrayList<>(); - // Pre-gen all the chunks - for (BlockVector2 chunk : region.getChunks()) { - chunkLoadings.add( - world.getChunkSource().getChunkFuture(chunk.x(), chunk.z(), ChunkStatus.FEATURES, true) - .thenApply(either -> either.orElse(null)) - ); - } - return chunkLoadings; - } - - @SuppressWarnings("deprecation") - @Nullable - private static ResourceKey createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType type) { - return switch (type) { - // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields - case TREE -> TreePlacements.OAK_CHECKED; - case BIG_TREE -> TreePlacements.FANCY_OAK_CHECKED; - case REDWOOD -> TreePlacements.SPRUCE_CHECKED; - case TALL_REDWOOD -> TreePlacements.MEGA_SPRUCE_CHECKED; - case MEGA_REDWOOD -> TreePlacements.MEGA_PINE_CHECKED; - case BIRCH -> TreePlacements.BIRCH_CHECKED; - case JUNGLE -> TreePlacements.MEGA_JUNGLE_TREE_CHECKED; - case SMALL_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; - case SHORT_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; - case JUNGLE_BUSH -> TreePlacements.JUNGLE_BUSH; - case SWAMP -> TreePlacements.OAK_CHECKED; - case ACACIA -> TreePlacements.ACACIA_CHECKED; - case DARK_OAK -> TreePlacements.DARK_OAK_CHECKED; - case TALL_BIRCH -> TreePlacements.SUPER_BIRCH_BEES_0002; - case WARPED_FUNGUS -> TreePlacements.WARPED_FUNGI; - case CRIMSON_FUNGUS -> TreePlacements.CRIMSON_FUNGI; - case CHORUS_PLANT -> EndPlacements.CHORUS_PLANT; - case MANGROVE -> TreePlacements.MANGROVE_CHECKED; - case TALL_MANGROVE -> TreePlacements.TALL_MANGROVE_CHECKED; - case CHERRY -> TreePlacements.CHERRY_CHECKED; - case PALE_OAK -> TreePlacements.PALE_OAK_CHECKED; - case PALE_OAK_CREAKING -> TreePlacements.PALE_OAK_CREAKING_CHECKED; - case RANDOM -> createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(com.sk89q.worldedit.util.TreeGenerator.TreeType.values().length)]); - default -> null; - }; - } - - @SuppressWarnings("deprecation") - @Override - public boolean generateTree(com.sk89q.worldedit.util.TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { - ServerLevel world = getWorld(); - PlacedFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) - .map(k -> world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(k)) - .orElse(null); - ServerChunkCache chunkManager = world.getChunkSource(); - if (type == com.sk89q.worldedit.util.TreeGenerator.TreeType.CHORUS_PLANT) { - position = position.add(0, 1, 0); - } - try (NeoForgeServerLevelDelegateProxy.LevelAndProxy proxyLevel = NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { - return generator != null && generator.place( - proxyLevel.level(), chunkManager.getGenerator(), random, - NeoForgeAdapter.toBlockPos(position) - ); - } - } - - @Override - public boolean generateTree(com.sk89q.worldedit.world.generation.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { - ServerLevel world = getWorld(); - PlacedFeature generator = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); - ServerChunkCache chunkManager = world.getChunkSource(); - try (NeoForgeServerLevelDelegateProxy.LevelAndProxy proxyLevel = NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { - return generator != null && generator.place( - proxyLevel.level(), chunkManager.getGenerator(), random, - NeoForgeAdapter.toBlockPos(position) - ); - } - } - - @Override - public boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { - ServerLevel world = getWorld(); - ConfiguredFeature feature = world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); - ServerChunkCache chunkManager = world.getChunkSource(); - try (NeoForgeServerLevelDelegateProxy.LevelAndProxy levelProxy = - NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { - return feature != null && feature.place(levelProxy.level(), chunkManager.getGenerator(), random, NeoForgeAdapter.toBlockPos(position)); - } catch (MaxChangedBlocksException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { - ServerLevel world = getWorld(); - Registry structureRegistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE); - Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id())); - if (structure == null) { - return false; - } - - ServerChunkCache chunkManager = world.getChunkSource(); - try (NeoForgeServerLevelDelegateProxy.LevelAndProxy levelProxy = - NeoForgeServerLevelDelegateProxy.newInstance(editSession, world)) { - ChunkPos chunkPos = new ChunkPos(new BlockPos(position.x(), position.y(), position.z())); - StructureStart structureStart = structure.generate( - structureRegistry.wrapAsHolder(structure), world.dimension(), world.registryAccess(), - chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), - world.getStructureManager(), world.getSeed(), chunkPos, 0, levelProxy.level(), - biome -> true - ); - - if (!structureStart.isValid()) { - return false; - } else { - BoundingBox boundingBox = structureStart.getBoundingBox(); - ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ())); - ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ())); - ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> - structureStart.placeInChunk( - levelProxy.level(), world.structureManager(), chunkManager.getGenerator(), world.getRandom(), - new BoundingBox( - chunkPosx.getMinBlockX(), world.getMinY(), chunkPosx.getMinBlockZ(), - chunkPosx.getMaxBlockX(), world.getMaxY(), chunkPosx.getMaxBlockZ() - ), - chunkPosx - ) - ); - return true; - } - } catch (MaxChangedBlocksException e) { - throw new RuntimeException(e); - } - } - - @Override - public void checkLoadedChunk(BlockVector3 pt) { - getWorld().getChunk(NeoForgeAdapter.toBlockPos(pt)); - } - - @Override - public void fixAfterFastMode(Iterable chunks) { - fixLighting(chunks); - } - - @Override - public void sendBiomeUpdates(Iterable chunks) { - List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); - for (BlockVector2 chunk : chunks) { - nativeChunks.add(getWorld().getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); - } - getWorld().getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); - } - - @Override - public void fixLighting(Iterable chunks) { - ServerLevel world = getWorld(); - for (BlockVector2 chunk : chunks) { - // Fetch the chunk after light initialization at least - // We'll be doing a full relight anyways, so we don't need to be LIGHT yet - world.getChunkSource().getLightEngine().lightChunk(world.getChunk( - chunk.x(), chunk.z(), ChunkStatus.INITIALIZE_LIGHT - ), false); - } - } - - @Override - public WeatherType getWeather() { - LevelData info = getWorld().getLevelData(); - if (info.isThundering()) { - return WeatherTypes.THUNDER_STORM; - } - if (info.isRaining()) { - return WeatherTypes.RAIN; - } - return WeatherTypes.CLEAR; - } - - @Override - public long getRemainingWeatherDuration() { - ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); - if (info.isThundering()) { - return info.getThunderTime(); - } - if (info.isRaining()) { - return info.getRainTime(); - } - return info.getClearWeatherTime(); - } - - @Override - public void setWeather(WeatherType weatherType) { - setWeather(weatherType, 0); - } - - @Override - public void setWeather(WeatherType weatherType, long duration) { - ServerLevelData info = (ServerLevelData) getWorld().getLevelData(); - if (weatherType == WeatherTypes.THUNDER_STORM) { - info.setClearWeatherTime(0); - info.setThundering(true); - info.setThunderTime((int) duration); - } else if (weatherType == WeatherTypes.RAIN) { - info.setClearWeatherTime(0); - info.setRaining(true); - info.setRainTime((int) duration); - } else if (weatherType == WeatherTypes.CLEAR) { - info.setRaining(false); - info.setThundering(false); - info.setClearWeatherTime((int) duration); - } - } - - @Override - public int getMinY() { - return getWorld().getMinY(); - } - - @Override - public int getMaxY() { - return getWorld().getMaxY(); - } - - @Override - public BlockVector3 getSpawnPosition() { - return NeoForgeAdapter.adapt(getWorld().getLevelData().getRespawnData().pos()); - } - - @Override - public BlockState getBlock(BlockVector3 position) { - net.minecraft.world.level.block.state.BlockState mcState = getWorld() - .getChunk(position.x() >> 4, position.z() >> 4) - .getBlockState(NeoForgeAdapter.toBlockPos(position)); - - return NeoForgeAdapter.adapt(mcState); - } - - @Override - public BaseBlock getFullBlock(BlockVector3 position) { - BlockPos pos = new BlockPos(position.x(), position.y(), position.z()); - BlockEntity tile = getWorld().getChunk(pos).getBlockEntity(pos); - - if (tile != null) { - var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, getWorld().registryAccess()); - tile.saveWithId(tagValueOutput); - net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult(); - return getBlock(position).toBaseBlock( - LazyReference.from(() -> NBTConverter.fromNative(tag)) - ); - } else { - return getBlock(position).toBaseBlock(); - } - } - - @Override - public int hashCode() { - return getWorld().hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } else if ((o instanceof NeoForgeWorld other)) { - Level otherWorld = other.worldRef.get(); - Level thisWorld = worldRef.get(); - return otherWorld != null && otherWorld.equals(thisWorld); - } else if (o instanceof com.sk89q.worldedit.world.World) { - return ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); - } else { - return false; - } - } - - @Override - public List getEntities(Region region) { - final ServerLevel world = getWorld(); - AABB box = new AABB( - NeoForgeAdapter.toVec3(region.getMinimumPoint()), - NeoForgeAdapter.toVec3(region.getMaximumPoint().add(BlockVector3.ONE)) - ); - List nmsEntities = world.getEntities( - (net.minecraft.world.entity.Entity) null, - box, - e -> region.contains(NeoForgeAdapter.adapt(e.blockPosition())) - ); - return nmsEntities.stream().map(NeoForgeEntity::new).collect(ImmutableList.toImmutableList()); - } - - @Override - public List getEntities() { - final ServerLevel world = getWorld(); - return Streams.stream(world.getAllEntities()) - .map(NeoForgeEntity::new) - .collect(ImmutableList.toImmutableList()); - } - - @Nullable - @Override - public Entity createEntity(Location location, BaseEntity entity) { - ServerLevel world = getWorld(); - String entityId = entity.getType().id(); - final Optional> entityType = EntityType.byString(entityId); - if (entityType.isEmpty()) { - return null; - } - LinCompoundTag nativeTag = entity.getNbt(); - net.minecraft.nbt.CompoundTag tag; - if (nativeTag != null) { - tag = NBTConverter.toNative(nativeTag); - removeUnwantedEntityTagsRecursively(tag); - } else { - tag = new net.minecraft.nbt.CompoundTag(); - } - tag.putString("id", entityId); - - net.minecraft.world.entity.Entity createdEntity = EntityType.loadEntityRecursive(tag, world, EntitySpawnReason.COMMAND, (loadedEntity) -> { - loadedEntity.absSnapTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - return loadedEntity; - }); - if (createdEntity != null) { - world.addFreshEntityWithPassengers(createdEntity); - return new NeoForgeEntity(createdEntity); - } - return null; - } - - private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { - for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { - tag.remove(name); - } - - // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive - tag.getList("Passengers").ifPresent(nbttaglist -> { - for (int i = 0; i < nbttaglist.size(); ++i) { - removeUnwantedEntityTagsRecursively(nbttaglist.getCompoundOrEmpty(i)); - } - }); - } - - @Override - public Mask createLiquidMask() { - return new AbstractExtentMask(this) { - @Override - public boolean test(BlockVector3 vector) { - return NeoForgeAdapter.adapt(getExtent().getBlock(vector)).getBlock() instanceof LiquidBlock; - } - }; - } - - @Override - public boolean isValid() { - return worldRef.get() != null; - } -} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java deleted file mode 100644 index fcb84a283a..0000000000 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorldEdit.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * 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.neoforge; - -import com.mojang.brigadier.ParseResults; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.command.util.PermissionCondition; -import com.sk89q.worldedit.event.platform.PlatformReadyEvent; -import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; -import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; -import com.sk89q.worldedit.event.platform.SessionIdleEvent; -import com.sk89q.worldedit.extension.platform.Capability; -import com.sk89q.worldedit.extension.platform.Platform; -import com.sk89q.worldedit.extension.platform.PlatformManager; -import com.sk89q.worldedit.internal.anvil.ChunkDeleter; -import com.sk89q.worldedit.internal.event.InteractionDebouncer; -import com.sk89q.worldedit.internal.util.LogManagerCompat; -import com.sk89q.worldedit.util.Direction; -import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.world.biome.BiomeCategory; -import com.sk89q.worldedit.world.biome.BiomeType; -import com.sk89q.worldedit.world.block.BlockCategory; -import com.sk89q.worldedit.world.block.BlockType; -import com.sk89q.worldedit.world.entity.EntityType; -import com.sk89q.worldedit.world.gamemode.GameModes; -import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; -import com.sk89q.worldedit.world.generation.StructureType; -import com.sk89q.worldedit.world.generation.TreeType; -import com.sk89q.worldedit.world.item.ItemCategory; -import com.sk89q.worldedit.world.item.ItemType; -import com.sk89q.worldedit.world.weather.WeatherTypes; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; -import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; -import net.minecraft.world.level.levelgen.feature.TreeFeature; -import net.minecraft.world.level.levelgen.placement.PlacedFeature; -import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.ModContainer; -import net.neoforged.fml.ModLoadingContext; -import net.neoforged.fml.common.Mod; -import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; -import net.neoforged.fml.loading.FMLPaths; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.CommandEvent; -import net.neoforged.neoforge.event.RegisterCommandsEvent; -import net.neoforged.neoforge.event.entity.player.PlayerEvent; -import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; -import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; -import net.neoforged.neoforge.event.server.ServerStartedEvent; -import net.neoforged.neoforge.event.server.ServerStoppingEvent; -import org.apache.logging.log4j.Logger; -import org.enginehub.piston.Command; -import org.enginehub.worldeditcui.protocol.CUIPacket; -import org.enginehub.worldeditcui.protocol.CUIPacketHandler; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; -import static com.sk89q.worldedit.neoforge.NeoForgeAdapter.adaptCommandSource; -import static com.sk89q.worldedit.neoforge.NeoForgeAdapter.adaptPlayer; - -/** - * The Forge implementation of WorldEdit. - */ -@Mod(NeoForgeWorldEdit.MOD_ID) -public class NeoForgeWorldEdit { - - private static final Logger LOGGER = LogManagerCompat.getLogger(); - public static final String MOD_ID = "worldedit"; - - private NeoForgePermissionsProvider provider; - - public static NeoForgeWorldEdit inst; - - private InteractionDebouncer debouncer; - private NeoForgePlatform platform; - private NeoForgeConfiguration config; - private Path workingDir; - - private ModContainer container; - - public NeoForgeWorldEdit(IEventBus modBus) { - inst = this; - - modBus.addListener(this::init); - - NeoForge.EVENT_BUS.register(ThreadSafeCache.getInstance()); - NeoForge.EVENT_BUS.register(this); - } - - private void init(FMLCommonSetupEvent event) { - this.container = ModLoadingContext.get().getActiveContainer(); - - // Setup working directory - workingDir = FMLPaths.CONFIGDIR.get().resolve("worldedit"); - if (!Files.exists(workingDir)) { - try { - Files.createDirectory(workingDir); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - CUIPacketHandler.instance().registerServerboundHandler(this::onCuiPacket); - - setupPlatform(); - - LOGGER.info("WorldEdit for NeoForge (version {}) is loaded", getInternalVersion()); - } - - private void setupPlatform() { - this.platform = new NeoForgePlatform(this); - debouncer = new InteractionDebouncer(platform); - - WorldEdit.getInstance().getPlatformManager().register(platform); - - config = new NeoForgeConfiguration(this); - - this.provider = new NeoForgePermissionsProvider.VanillaPermissionsProvider(platform); - } - - private void setupRegistries(MinecraftServer server) { - // Blocks - for (ResourceLocation name : BuiltInRegistries.BLOCK.keySet()) { - String key = name.toString(); - if (BlockType.REGISTRY.get(key) == null) { - BlockType.REGISTRY.register(key, new BlockType(key, - input -> NeoForgeAdapter.adapt(NeoForgeAdapter.adapt(input.getBlockType()).defaultBlockState()))); - } - } - // Items - for (ResourceLocation name : BuiltInRegistries.ITEM.keySet()) { - String key = name.toString(); - if (ItemType.REGISTRY.get(key) == null) { - ItemType.REGISTRY.register(key, new ItemType(key)); - } - } - // Entities - for (ResourceLocation name : BuiltInRegistries.ENTITY_TYPE.keySet()) { - String key = name.toString(); - if (EntityType.REGISTRY.get(key) == null) { - EntityType.REGISTRY.register(key, new EntityType(key)); - } - } - // Biomes - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { - String key = name.toString(); - if (BiomeType.REGISTRY.get(key) == null) { - BiomeType.REGISTRY.register(key, new BiomeType(key)); - } - } - // Tags - server.registryAccess().lookupOrThrow(Registries.BLOCK).getTags().map(t -> t.key().location()).forEach(name -> { - String key = name.toString(); - if (BlockCategory.REGISTRY.get(key) == null) { - BlockCategory.REGISTRY.register(key, new BlockCategory(key)); - } - }); - server.registryAccess().lookupOrThrow(Registries.ITEM).getTags().map(t -> t.key().location()).forEach(name -> { - String key = name.toString(); - if (ItemCategory.REGISTRY.get(key) == null) { - ItemCategory.REGISTRY.register(key, new ItemCategory(key)); - } - }); - Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); - biomeRegistry.getTags().forEach(tag -> { - String key = tag.key().location().toString(); - if (BiomeCategory.REGISTRY.get(key) == null) { - BiomeCategory.REGISTRY.register(key, new BiomeCategory( - key, - () -> biomeRegistry.get(tag.key()) - .stream() - .flatMap(HolderSet.Named::stream) - .map(Holder::value) - .map(NeoForgeAdapter::adapt) - .collect(Collectors.toSet())) - ); - } - }); - // Features - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { - String key = name.toString(); - if (ConfiguredFeatureType.REGISTRY.get(key) == null) { - ConfiguredFeatureType.REGISTRY.register(key, new ConfiguredFeatureType(key)); - } - } - // Structures - for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { - String key = name.toString(); - if (StructureType.REGISTRY.get(key) == null) { - StructureType.REGISTRY.register(key, new StructureType(key)); - } - } - // Trees - Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); - for (ResourceLocation name : placedFeatureRegistry.keySet()) { - // Do some hackery to make sure this is a tree - var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); - if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { - String key = name.toString(); - if (TreeType.REGISTRY.get(key) == null) { - TreeType.REGISTRY.register(key, new TreeType(key)); - } - } - } - - // ... :| - GameModes.get(""); - WeatherTypes.get(""); - com.sk89q.worldedit.registry.Registries.get(""); - } - - @SubscribeEvent - public void registerCommands(RegisterCommandsEvent event) { - WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); - - PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); - Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); - if (commandsPlatform != platform || !platform.isHookingEvents()) { - // We're not in control of commands/events -- do not register. - return; - } - - List commands = manager.getPlatformCommandManager().getCommandManager() - .getAllCommands().toList(); - for (Command command : commands) { - CommandWrapper.register(event.getDispatcher(), command); - Set perms = command.getCondition().as(PermissionCondition.class) - .map(PermissionCondition::getPermissions) - .orElseGet(Collections::emptySet); - if (!perms.isEmpty()) { - perms.forEach(getPermissionsProvider()::registerPermission); - } - } - } - - @SubscribeEvent - public void serverAboutToStart(ServerAboutToStartEvent event) { - final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME); - if (Files.exists(delChunks)) { - ChunkDeleter.runFromFile(delChunks, true); - } - } - - @SubscribeEvent - public void serverStopping(ServerStoppingEvent event) { - WorldEdit worldEdit = WorldEdit.getInstance(); - worldEdit.getSessionManager().unload(); - WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); - } - - @SubscribeEvent - public void serverStarted(ServerStartedEvent event) { - setupRegistries(event.getServer()); - - config.load(); - WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); - } - - private boolean skipEvents() { - return platform == null || !platform.isHookingEvents(); - } - - private boolean skipInteractionEvent(Player player, InteractionHand hand) { - return skipEvents() || hand != InteractionHand.MAIN_HAND || player.level().isClientSide() || !(player instanceof ServerPlayer); - } - - @SubscribeEvent - public void onLeftClickBlock(PlayerInteractEvent.LeftClickBlock event) { - if (skipInteractionEvent(event.getEntity(), event.getHand()) || event.getUseItem().isFalse()) { - return; - } - - ServerPlayer playerEntity = (ServerPlayer) event.getEntity(); - WorldEdit we = WorldEdit.getInstance(); - NeoForgePlayer player = adaptPlayer(playerEntity); - NeoForgeWorld world = getWorld(playerEntity.level()); - Direction direction = NeoForgeAdapter.adaptEnumFacing(event.getFace()); - - BlockPos blockPos = event.getPos(); - Location pos = new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()); - - boolean result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player); - debouncer.setLastInteraction(player, result); - - if (result) { - event.setCanceled(true); - } - } - - @SubscribeEvent - public void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) { - if (skipInteractionEvent(event.getEntity(), event.getHand()) || event.getUseItem().isFalse()) { - return; - } - - ServerPlayer playerEntity = (ServerPlayer) event.getEntity(); - WorldEdit we = WorldEdit.getInstance(); - NeoForgePlayer player = adaptPlayer(playerEntity); - NeoForgeWorld world = getWorld(playerEntity.level()); - Direction direction = NeoForgeAdapter.adaptEnumFacing(event.getFace()); - - BlockPos blockPos = event.getPos(); - Location pos = new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()); - - boolean result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player); - debouncer.setLastInteraction(player, result); - - if (result) { - event.setCanceled(true); - } - } - - public void onLeftClickAir(ServerPlayer playerEntity, InteractionHand hand) { - if (skipInteractionEvent(playerEntity, hand)) { - return; - } - - WorldEdit we = WorldEdit.getInstance(); - NeoForgePlayer player = adaptPlayer(playerEntity); - - Optional previousResult = debouncer.getDuplicateInteractionResult(player); - if (previousResult.isPresent()) { - return; - } - - boolean result = we.handleArmSwing(player); - debouncer.setLastInteraction(player, result); - } - - @SubscribeEvent - public void onRightClickItem(PlayerInteractEvent.RightClickItem event) { - if (skipInteractionEvent(event.getEntity(), event.getHand())) { - return; - } - - ServerPlayer playerEntity = (ServerPlayer) event.getEntity(); - WorldEdit we = WorldEdit.getInstance(); - NeoForgePlayer player = adaptPlayer(playerEntity); - - Optional previousResult = debouncer.getDuplicateInteractionResult(player); - if (previousResult.isPresent()) { - if (previousResult.get()) { - event.setCanceled(true); - } - return; - } - - boolean result = we.handleRightClick(player); - debouncer.setLastInteraction(player, result); - - if (result) { - event.setCanceled(true); - } - } - - @SubscribeEvent - public void onCommandEvent(CommandEvent event) throws CommandSyntaxException { - ParseResults parseResults = event.getParseResults(); - if (parseResults.getContext().getSource().getEntity() instanceof ServerPlayer player && player.level().isClientSide()) { - return; - } - if (parseResults.getContext().getCommand() != CommandWrapper.FAKE_COMMAND) { - return; - } - event.setCanceled(true); - WorldEdit.getInstance().getEventBus().post(new com.sk89q.worldedit.event.platform.CommandEvent( - adaptCommandSource(parseResults.getContext().getSource()), - "/" + parseResults.getReader().getString() - )); - } - - @SubscribeEvent - public void onPlayerLogOut(PlayerEvent.PlayerLoggedOutEvent event) { - if (event.getEntity() instanceof ServerPlayer player) { - debouncer.clearInteraction(adaptPlayer(player)); - - WorldEdit.getInstance().getEventBus() - .post(new SessionIdleEvent(new NeoForgePlayer.SessionKeyImpl(player))); - } - } - - private void onCuiPacket(CUIPacket payload, CUIPacketHandler.PacketContext context) { - if (!(context.player() instanceof ServerPlayer player)) { - // Ignore - this is not a server-bound packet - return; - } - NeoForgePlayer actor = NeoForgeAdapter.adaptPlayer(player); - LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor); - session.handleCUIInitializationMessage(payload.eventType(), payload.args(), actor); - } - - /** - * Get the configuration. - * - * @return the Forge configuration - */ - NeoForgeConfiguration getConfig() { - return this.config; - } - - /** - * Get the session for a player. - * - * @param player the player - * @return the session - */ - public LocalSession getSession(ServerPlayer player) { - checkNotNull(player); - return WorldEdit.getInstance().getSessionManager().get(adaptPlayer(player)); - } - - /** - * Get the WorldEdit proxy for the given world. - * - * @param world the world - * @return the WorldEdit world - */ - public NeoForgeWorld getWorld(ServerLevel world) { - checkNotNull(world); - return new NeoForgeWorld(world); - } - - /** - * Get the WorldEdit proxy for the platform. - * - * @return the WorldEdit platform - */ - public Platform getPlatform() { - return this.platform; - } - - /** - * Get the working directory where WorldEdit's files are stored. - * - * @return the working directory - */ - public Path getWorkingDir() { - return this.workingDir; - } - - /** - * Get the version of the WorldEdit-for-Forge implementation. - * - * @return a version string - */ - String getInternalVersion() { - return container.getModInfo().getVersion().toString(); - } - - public void setPermissionsProvider(NeoForgePermissionsProvider provider) { - this.provider = provider; - } - - public NeoForgePermissionsProvider getPermissionsProvider() { - return provider; - } - -} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java index fd654fc078..db945669af 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java @@ -19,124 +19,93 @@ package com.sk89q.worldedit.sponge; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.flowpowered.math.vector.Vector3d; +import com.flowpowered.math.vector.Vector3i; import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; -import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.sponge.internal.NbtAdapter; -import com.sk89q.worldedit.sponge.internal.SpongeWorldNativeAccess; +import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.world.AbstractWorld; -import com.sk89q.worldedit.world.RegenOptions; -import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeType; 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.generation.TreeType; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; -import net.minecraft.core.BlockPos; -import net.minecraft.core.registries.Registries; -import net.minecraft.data.worldgen.placement.EndPlacements; -import net.minecraft.data.worldgen.placement.TreePlacements; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.minecraft.world.level.levelgen.placement.PlacedFeature; -import org.apache.logging.log4j.Logger; -import org.enginehub.linbus.tree.LinCompoundTag; -import org.enginehub.linbus.tree.LinIntTag; -import org.enginehub.linbus.tree.LinStringTag; -import org.spongepowered.api.ResourceKey; -import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; -import org.spongepowered.api.block.entity.BlockEntity; -import org.spongepowered.api.block.entity.BlockEntityArchetype; -import org.spongepowered.api.block.entity.BlockEntityType; -import org.spongepowered.api.data.Keys; -import org.spongepowered.api.entity.EntityArchetype; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.block.tileentity.TileEntity; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.data.property.block.GroundLuminanceProperty; +import org.spongepowered.api.data.property.block.SkyLuminanceProperty; import org.spongepowered.api.entity.EntityType; import org.spongepowered.api.entity.EntityTypes; -import org.spongepowered.api.entity.Item; -import org.spongepowered.api.registry.RegistryTypes; -import org.spongepowered.api.util.Ticks; import org.spongepowered.api.world.BlockChangeFlags; -import org.spongepowered.api.world.LightTypes; -import org.spongepowered.api.world.SerializationBehavior; -import org.spongepowered.api.world.generation.config.WorldGenerationConfig; -import org.spongepowered.api.world.server.ServerLocation; -import org.spongepowered.api.world.server.ServerWorld; -import org.spongepowered.api.world.server.WorldArchetype; -import org.spongepowered.api.world.server.WorldArchetypeType; -import org.spongepowered.api.world.server.storage.ServerWorldProperties; -import org.spongepowered.api.world.volume.stream.StreamOptions; -import org.spongepowered.math.vector.Vector3d; -import org.spongepowered.math.vector.Vector3i; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.weather.Weather; import java.lang.ref.WeakReference; import java.nio.file.Path; -import java.util.Collection; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkNotNull; +import javax.annotation.Nullable; /** * An adapter to Minecraft worlds for WorldEdit. */ -public final class SpongeWorld extends AbstractWorld { - - private static final RandomSource random = RandomSource.create(); - private static final Logger LOGGER = LogManagerCompat.getLogger(); +public abstract class SpongeWorld extends AbstractWorld { - private final WeakReference worldRef; - private final SpongeWorldNativeAccess worldNativeAccess; + private final WeakReference worldRef; /** * Construct a new world. * * @param world the world */ - SpongeWorld(ServerWorld world) { + protected SpongeWorld(World world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.worldNativeAccess = new SpongeWorldNativeAccess(new WeakReference<>((ServerLevel) world)); } /** * Get the underlying handle to the world. * * @return the world - * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was - * unloaded) + * @throws WorldEditException thrown if a reference to the world was lost (i.e. world was unloaded) + */ + public World getWorldChecked() throws WorldEditException { + World world = worldRef.get(); + if (world != null) { + return world; + } else { + throw new WorldReferenceLostException("The reference to the world was lost (i.e. the world may have been unloaded)"); + } + } + + /** + * Get the underlying handle to the world. + * + * @return the world + * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded) */ - ServerWorld getWorld() { - ServerWorld world = worldRef.get(); + public World getWorld() { + World world = worldRef.get(); if (world != null) { return world; } else { @@ -144,272 +113,109 @@ ServerWorld getWorld() { } } - // This is sus but leaving it for later world name/id reworks @Override public String getName() { - return getWorld().key().asString(); + return getWorld().getName(); } @Override - public String id() { - return getWorld().key().asString(); + public String getId() { + return getName().replace(" ", "_").toLowerCase(Locale.ROOT) + + getWorld().getDimension().getType().getName().toLowerCase(Locale.ROOT); } @Override public Path getStoragePath() { - return getWorld().directory(); - } - - @Override - public BlockState getBlock(BlockVector3 position) { - return SpongeAdapter.adapt(getWorld().block( - position.x(), position.y(), position.z() - )); - } - - @Override - public BaseBlock getFullBlock(BlockVector3 position) { - BlockEntity blockEntity = getWorld().blockEntity( - position.x(), position.y(), position.z() - ).orElse(null); - LinCompoundTag blockEntityData = null; - if (blockEntity != null) { - BlockEntityArchetype blockEntityArchetype = blockEntity.createArchetype(); - BlockEntityType blockEntityType = blockEntityArchetype.blockEntityType(); - ResourceKey blockEntityId = blockEntityType.key(RegistryTypes.BLOCK_ENTITY_TYPE); - blockEntityData = NbtAdapter.adaptToWorldEdit(blockEntityArchetype.blockEntityData()); - - // Add ID and position since Sponge's #blockEntityData does not save metadata - LinCompoundTag.Builder fullBlockEntityDataBuilder = blockEntityData.toBuilder(); - fullBlockEntityDataBuilder.put("id", LinStringTag.of(blockEntityId.formatted())); - fullBlockEntityDataBuilder.put("x", LinIntTag.of(position.x())); - fullBlockEntityDataBuilder.put("y", LinIntTag.of(position.y())); - fullBlockEntityDataBuilder.put("z", LinIntTag.of(position.z())); - blockEntityData = fullBlockEntityDataBuilder.build(); - } - return getBlock(position).toBaseBlock(blockEntityData); + return getWorld().getDirectory(); } - @Override - public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { - checkNotNull(position); - checkNotNull(block); - - ServerWorld world = getWorld(); - - org.spongepowered.api.block.BlockState newState = SpongeAdapter.adapt(block.toImmutableState()); - - boolean didSet = world.setBlock( - position.x(), position.y(), position.z(), - newState, - BlockChangeFlags.NONE - .withUpdateNeighbors(sideEffects.shouldApply(SideEffect.NEIGHBORS)) - .withNotifyClients(true) - .withPhysics(sideEffects.shouldApply(SideEffect.UPDATE)) - .withNotifyObservers(sideEffects.shouldApply(SideEffect.UPDATE)) - .withLightingUpdates(sideEffects.shouldApply(SideEffect.LIGHTING)) - .withPathfindingUpdates(sideEffects.shouldApply(SideEffect.ENTITY_AI)) - .withNeighborDropsAllowed(false) - .withBlocksMoving(false) - .withForcedReRender(false) - .withIgnoreRender(false) - .withPerformBlockDestruction(false) - ); - if (!didSet) { - // still update NBT if the block is the same - if (world.block(position.x(), position.y(), position.z()) == newState) { - didSet = block.toBaseBlock().getNbt() != null; + @SuppressWarnings("WeakerAccess") + protected BlockState getBlockState(BlockStateHolder block) { + if (block instanceof com.sk89q.worldedit.world.block.BlockState) { + BlockState state = Sponge.getRegistry().getType(BlockType.class, block.getBlockType().getId()).orElse(BlockTypes.AIR).getDefaultState(); + for (Map.Entry, Object> entry : block.getStates().entrySet()) { + // TODO Convert across states } + return state; + } else { + throw new UnsupportedOperationException("Missing Sponge adapter for WorldEdit!"); } + } - // Create the TileEntity - if (didSet && block instanceof BaseBlock baseBlock) { - LinCompoundTag nbt = baseBlock.getNbt(); - if (nbt != null) { - BlockEntityArchetype.builder() - .state(newState) - .blockEntity( - Sponge.game().registry(RegistryTypes.BLOCK_ENTITY_TYPE) - .value(ResourceKey.resolve(baseBlock.getNbtId())) - ) - .blockEntityData(NbtAdapter.adaptFromWorldEdit(nbt)) - .build() - .apply(ServerLocation.of(world, position.x(), position.y(), position.z())); - } - } + @SuppressWarnings("WeakerAccess") + protected abstract void applyTileEntityData(TileEntity entity, BaseBlock block); - return true; - } + private static final BlockSnapshot.Builder builder = BlockSnapshot.builder(); @Override - public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { + public > boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException { checkNotNull(position); + checkNotNull(block); - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); - - return Sets.intersection( - SpongeWorldEdit.inst().getInternalPlatform().getSupportedSideEffects(), - sideEffectSet.getSideEffectsToApply() - ); - } - - @Override - public boolean clearContainerBlockContents(BlockVector3 position) { - getWorld().removeBlockEntity(position.x(), position.y(), position.z()); - return true; - } + World world = getWorldChecked(); - @Override - public boolean regenerate(Region region, Extent extent, RegenOptions options) { - Server server = Sponge.server(); - - final String id = "worldedittemp_" + getWorld().key().value(); - final ResourceKey key = ResourceKey.of("worldedit", id); - - WorldGenerationConfig worldGenConfig = WorldGenerationConfig.builder() - .from(getWorld().properties().worldGenerationConfig()) - .seed(options.getSeed().orElse(getWorld().properties().worldGenerationConfig().seed())) - .build(); - - WorldArchetypeType worldArchetypeType = WorldArchetypeType.builder() - .chunkGenerator(getWorld().generator()) - .worldType(getWorld().worldType()) - .build(); - - WorldArchetype worldArchetype = WorldArchetype.builder() - .generationConfig(worldGenConfig) - .type(worldArchetypeType) - .build(); - - ServerWorldProperties.LoadOptions loadOptions = ServerWorldProperties.LoadOptions.create( - worldArchetype, - (properties) -> { - properties.copyFrom(getWorld().properties()); - properties.offer(Keys.SERIALIZATION_BEHAVIOR, SerializationBehavior.NONE); - properties.offer(Keys.IS_LOAD_ON_STARTUP, false); - } - ); + // First set the block + Vector3i pos = new Vector3i(position.getX(), position.getY(), position.getZ()); + BlockState newState = getBlockState(block); - ServerWorld tempWorld; - try { - tempWorld = server.worldManager().loadWorld(key, loadOptions).get().get(); - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("Failed to load temp world", e); - return false; - } + BlockSnapshot snapshot = builder.reset() + .blockState(newState) + .position(pos) + .world(world.getProperties()) + .build(); - try { - // Pre-gen all the chunks - // We need to also pull one more chunk in every direction - CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 16, 16), region.getMaximumPoint().add(16, 16, 16)); - for (BlockVector3 chunk : expandedPreGen.getChunkCubes()) { - tempWorld.loadChunk(chunk.x(), chunk.y(), chunk.z(), true); - } + snapshot.restore(true, notifyAndLight ? BlockChangeFlags.ALL : BlockChangeFlags.NONE); - World from = SpongeAdapter.adapt(tempWorld); - for (BlockVector3 vec : region) { - extent.setBlock(vec, from.getFullBlock(vec)); - if (options.shouldRegenBiomes()) { - extent.setBiome(vec, from.getBiome(vec)); - } - } - } catch (WorldEditException e) { - throw new RuntimeException(e); - } finally { - // Remove temp world - server.worldManager().unloadWorld(key).thenRun(() -> server.worldManager().deleteWorld(key)); + // Create the TileEntity + if (block instanceof BaseBlock && ((BaseBlock) block).hasNbtData()) { + // Kill the old TileEntity + world.getTileEntity(pos).ifPresent(tileEntity -> applyTileEntityData(tileEntity, (BaseBlock) block)); } return true; } - - @SuppressWarnings("deprecation") - @Nullable - private static net.minecraft.resources.ResourceKey createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType type) { - return switch (type) { - // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields - case TREE -> TreePlacements.OAK_CHECKED; - case BIG_TREE -> TreePlacements.FANCY_OAK_CHECKED; - case REDWOOD -> TreePlacements.SPRUCE_CHECKED; - case TALL_REDWOOD -> TreePlacements.MEGA_SPRUCE_CHECKED; - case MEGA_REDWOOD -> TreePlacements.MEGA_PINE_CHECKED; - case BIRCH -> TreePlacements.BIRCH_CHECKED; - case JUNGLE -> TreePlacements.MEGA_JUNGLE_TREE_CHECKED; - case SMALL_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; - case SHORT_JUNGLE -> TreePlacements.JUNGLE_TREE_CHECKED; - case JUNGLE_BUSH -> TreePlacements.JUNGLE_BUSH; - case SWAMP -> TreePlacements.OAK_CHECKED; - case ACACIA -> TreePlacements.ACACIA_CHECKED; - case DARK_OAK -> TreePlacements.DARK_OAK_CHECKED; - case TALL_BIRCH -> TreePlacements.SUPER_BIRCH_BEES_0002; - case WARPED_FUNGUS -> TreePlacements.WARPED_FUNGI; - case CRIMSON_FUNGUS -> TreePlacements.CRIMSON_FUNGI; - case CHORUS_PLANT -> EndPlacements.CHORUS_PLANT; - case MANGROVE -> TreePlacements.MANGROVE_CHECKED; - case TALL_MANGROVE -> TreePlacements.TALL_MANGROVE_CHECKED; - case CHERRY -> TreePlacements.CHERRY_CHECKED; - case PALE_OAK -> TreePlacements.PALE_OAK_CHECKED; - case PALE_OAK_CREAKING -> TreePlacements.PALE_OAK_CREAKING_CHECKED; - case RANDOM -> createTreeFeatureGenerator(com.sk89q.worldedit.util.TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(com.sk89q.worldedit.util.TreeGenerator.TreeType.values().length)]); - default -> null; - }; - } - - @SuppressWarnings("deprecation") @Override - public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) { - ServerLevel world = (ServerLevel) getWorld(); - PlacedFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) - .map(k -> world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(k)) - .orElse(null); - return generator != null && generator.place( - world, world.getChunkSource().getGenerator(), random, - new BlockPos(position.x(), position.y(), position.z()) - ); + public boolean notifyAndLightBlock(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType) throws WorldEditException { + // TODO Move this to adapter + return false; } @Override - public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { - ServerLevel world = (ServerLevel) getWorld(); - PlacedFeature generator = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); - return generator != null && generator.place( - world, world.getChunkSource().getGenerator(), random, - new BlockPos(position.x(), position.y(), position.z()) - ); + public boolean regenerate(Region region, EditSession editSession) { + return false; } @Override public int getBlockLightLevel(BlockVector3 position) { checkNotNull(position); - int skyLight = getWorld().light(LightTypes.SKY, position.x(), position.y(), position.z()); - int groundLight = getWorld().light(LightTypes.BLOCK, position.x(), position.y(), position.z()); + BlockState state = getWorld().getBlock(new Vector3i(position.getX(), position.getY(), position.getZ())); + + Optional groundLuminanceProperty = state.getProperty(GroundLuminanceProperty.class); + Optional skyLuminanceProperty = state.getProperty(SkyLuminanceProperty.class); + + if (!groundLuminanceProperty.isPresent() || !skyLuminanceProperty.isPresent()) { + return 0; + } + + //noinspection ConstantConditions + return (int) Math.max(groundLuminanceProperty.get().getValue(), skyLuminanceProperty.get().getValue()); - return Math.max(skyLight, groundLight); } @Override - public BiomeType getBiome(BlockVector3 position) { + public BiomeType getBiome(BlockVector2 position) { checkNotNull(position); - return BiomeType.REGISTRY.get( - getWorld().registry(RegistryTypes.BIOME) - .valueKey(getWorld().biome(position.x(), position.y(), position.z())) - .asString() - ); + return SpongeAdapter.adapt(getWorld().getBiome(position.getBlockX(), 0, position.getBlockZ())); } @Override - public boolean setBiome(BlockVector3 position, BiomeType biome) { + public boolean setBiome(BlockVector2 position, BiomeType biome) { checkNotNull(position); checkNotNull(biome); - getWorld().setBiome( - position.x(), position.y(), position.z(), - getWorld().registry(RegistryTypes.BIOME).value( - ResourceKey.resolve(biome.id()) - ) - ); + getWorld().setBiome(position.getBlockX(), 0, position.getBlockZ(), SpongeAdapter.adapt(biome)); return true; } @@ -422,32 +228,18 @@ public void dropItem(Vector3 position, BaseItemStack item) { return; } - Item itemEntity = getWorld().createEntity( - EntityTypes.ITEM, - new Vector3d(position.x(), position.y(), position.z()) + org.spongepowered.api.entity.Entity entity = getWorld().createEntity( + EntityTypes.ITEM, + new Vector3d(position.getX(), position.getY(), position.getZ()) ); - itemEntity.item().set( - SpongeAdapter.adapt(item).asImmutable() - ); - getWorld().spawnEntity(itemEntity); + entity.offer(Keys.REPRESENTED_ITEM, SpongeWorldEdit.toSpongeItemStack(item).createSnapshot()); + getWorld().spawnEntity(entity); } @Override public void simulateBlockMine(BlockVector3 position) { - getWorld().destroyBlock( - new Vector3i(position.x(), position.y(), position.z()), - true - ); - } - - @Override - public boolean canPlaceAt(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState blockState) { - return ((net.minecraft.world.level.block.state.BlockState) SpongeAdapter.adapt(blockState)) - .canSurvive( - ((LevelReader) getWorld()), - new BlockPos(position.x(), position.y(), position.z()) - ); + // TODO } @Override @@ -455,117 +247,104 @@ public int hashCode() { return getWorld().hashCode(); } - @Override - public int getMaxY() { - return getWorld().max().y(); - } - - @Override - public int getMinY() { - return getWorld().min().y(); - } - @Override public boolean equals(Object o) { if (o == null) { return false; - } else if ((o instanceof SpongeWorld other)) { - ServerWorld otherWorld = other.worldRef.get(); - ServerWorld thisWorld = worldRef.get(); + } else if ((o instanceof SpongeWorld)) { + SpongeWorld other = ((SpongeWorld) o); + World otherWorld = other.worldRef.get(); + World thisWorld = worldRef.get(); return otherWorld != null && otherWorld.equals(thisWorld); } else { return o instanceof com.sk89q.worldedit.world.World - && ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); + && ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); } } @Override public List getEntities(Region region) { - return getWorld() - .entityStream( - SpongeAdapter.adaptVector3i(region.getMinimumPoint()), - SpongeAdapter.adaptVector3i(region.getMaximumPoint()), - // We don't need to force load or clone to copy entities - StreamOptions.builder() - .setCarbonCopy(false) - .setLoadingStyle(StreamOptions.LoadingStyle.NONE) - .build() - ) - .toStream() - .map(ve -> new SpongeEntity(ve.type())) - .collect(Collectors.toList()); + List entities = new ArrayList<>(); + for (org.spongepowered.api.entity.Entity entity : getWorld().getEntities()) { + org.spongepowered.api.world.Location loc = entity.getLocation(); + if (region.contains(BlockVector3.at(loc.getX(), loc.getY(), loc.getZ()))) { + entities.add(new SpongeEntity(entity)); + } + } + return entities; } @Override public List getEntities() { - return getWorld().entities().stream() - .map(SpongeEntity::new) - .collect(Collectors.toList()); + List entities = new ArrayList<>(); + for (org.spongepowered.api.entity.Entity entity : getWorld().getEntities()) { + entities.add(new SpongeEntity(entity)); + } + return entities; } + protected abstract void applyEntityData(org.spongepowered.api.entity.Entity entity, BaseEntity data); + @Nullable @Override public Entity createEntity(Location location, BaseEntity entity) { - Optional> entityType = Sponge.game().registry(RegistryTypes.ENTITY_TYPE) - .findValue(ResourceKey.resolve(entity.getType().id())); - if (entityType.isEmpty()) { - return null; - } - EntityArchetype.Builder builder = EntityArchetype.builder().type(entityType.get()); - var nativeTag = entity.getNbt(); - if (nativeTag != null) { - builder.entityData(NbtAdapter.adaptFromWorldEdit(nativeTag)); + World world = getWorld(); + + EntityType entityType = Sponge.getRegistry().getType(EntityType.class, entity.getType().getId()).get(); + Vector3d pos = new Vector3d(location.getX(), location.getY(), location.getZ()); + + org.spongepowered.api.entity.Entity newEnt = world.createEntity(entityType, pos); + if (entity.hasNbtData()) { + applyEntityData(newEnt, entity); } - return builder.build().apply(SpongeAdapter.adapt(location)).map(SpongeEntity::new).orElse(null); - } - @Override - public void sendBiomeUpdates(Iterable chunks) { - List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); - for (BlockVector2 chunk : chunks) { - nativeChunks.add(((Level) getWorld()).getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); + // Overwrite any data set by the NBT application + Vector3 dir = location.getDirection(); + + newEnt.setLocationAndRotation( + new org.spongepowered.api.world.Location<>(getWorld(), pos), + new Vector3d(dir.getX(), dir.getY(), dir.getZ()) + ); + + if (world.spawnEntity(newEnt)) { + return new SpongeEntity(newEnt); } - ((ServerLevel) getWorld()).getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); + + return null; } @Override public WeatherType getWeather() { - return WeatherTypes.get( - getWorld().weather().type().key(RegistryTypes.WEATHER_TYPE).asString() - ); + return WeatherTypes.get(getWorld().getWeather().getId()); } @Override public long getRemainingWeatherDuration() { - return getWorld().weather().remainingDuration().ticks(); + return getWorld().getRemainingDuration(); } @Override public void setWeather(WeatherType weatherType) { - getWorld().setWeather( - Sponge.game().registry(RegistryTypes.WEATHER_TYPE).value( - ResourceKey.resolve(weatherType.id()) - ) - ); + getWorld().setWeather(Sponge.getRegistry().getType(Weather.class, weatherType.getId()).get()); } @Override public void setWeather(WeatherType weatherType, long duration) { - getWorld().setWeather( - Sponge.game().registry(RegistryTypes.WEATHER_TYPE).value( - ResourceKey.resolve(weatherType.id()) - ), - Ticks.of(duration) - ); + getWorld().setWeather(Sponge.getRegistry().getType(Weather.class, weatherType.getId()).get(), duration); } @Override public BlockVector3 getSpawnPosition() { - return SpongeAdapter.adaptVector3i(getWorld().properties().spawnPosition()); + return SpongeAdapter.asBlockVector(getWorld().getSpawnLocation()); } - @Override - public boolean isValid() { - return worldRef.get() != null; + /** + * Thrown when the reference to the world is lost. + */ + private static class WorldReferenceLostException extends WorldEditException { + private WorldReferenceLostException(String message) { + super(message); + } } + } diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java index ecc6506007..194ffee159 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java @@ -19,306 +19,287 @@ package com.sk89q.worldedit.sponge; -import com.google.common.base.Joiner; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; + import com.google.inject.Inject; +import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.command.util.PermissionCondition; -import com.sk89q.worldedit.event.platform.CommandEvent; -import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; +import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; -import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; -import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; -import com.sk89q.worldedit.extension.platform.PlatformManager; import com.sk89q.worldedit.internal.anvil.ChunkDeleter; -import com.sk89q.worldedit.internal.command.CommandUtil; -import com.sk89q.worldedit.registry.Registries; +import com.sk89q.worldedit.sponge.adapter.AdapterLoadException; +import com.sk89q.worldedit.sponge.adapter.SpongeImplAdapter; +import com.sk89q.worldedit.sponge.adapter.SpongeImplLoader; import com.sk89q.worldedit.sponge.config.SpongeConfiguration; -import com.sk89q.worldedit.world.biome.BiomeCategory; -import com.sk89q.worldedit.world.biome.BiomeType; -import com.sk89q.worldedit.world.block.BlockCategory; -import com.sk89q.worldedit.world.generation.TreeType; -import com.sk89q.worldedit.world.item.ItemCategory; -import net.kyori.adventure.audience.Audience; +import org.bstats.sponge.Metrics2; import org.apache.logging.log4j.Logger; -import org.bstats.sponge.Metrics; -import org.spongepowered.api.ResourceKey; -import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; import org.spongepowered.api.block.BlockType; -import org.spongepowered.api.block.entity.BlockEntity; -import org.spongepowered.api.block.entity.CommandBlock; -import org.spongepowered.api.command.Command; -import org.spongepowered.api.command.CommandCause; -import org.spongepowered.api.command.CommandCompletion; -import org.spongepowered.api.command.CommandResult; -import org.spongepowered.api.command.parameter.ArgumentReader; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.config.ConfigDir; -import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; -import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; -import org.spongepowered.api.event.lifecycle.StartedEngineEvent; -import org.spongepowered.api.event.lifecycle.StartingEngineEvent; -import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; -import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.event.block.InteractBlockEvent; +import org.spongepowered.api.event.filter.cause.Root; +import org.spongepowered.api.event.game.state.GameAboutToStartServerEvent; +import org.spongepowered.api.event.game.state.GameInitializationEvent; +import org.spongepowered.api.event.game.state.GamePostInitializationEvent; +import org.spongepowered.api.event.game.state.GamePreInitializationEvent; +import org.spongepowered.api.event.game.state.GameStartedServerEvent; +import org.spongepowered.api.event.game.state.GameStoppingServerEvent; +import org.spongepowered.api.event.item.inventory.InteractItemEvent; +import org.spongepowered.api.event.network.ClientConnectionEvent; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.scheduler.Task; -import org.spongepowered.api.world.LocatableBlock; -import org.spongepowered.api.world.generation.feature.FeatureTypes; -import org.spongepowered.api.world.server.ServerWorld; -import org.spongepowered.plugin.PluginContainer; -import org.spongepowered.plugin.builtin.jvm.Plugin; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; -import java.lang.invoke.MethodHandles; +import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; -import static java.util.stream.Collectors.toList; /** * The Sponge implementation of WorldEdit. */ -@Plugin(SpongeWorldEdit.MOD_ID) +@Plugin(id = SpongeWorldEdit.MOD_ID, name = "WorldEdit", + description = "WorldEdit is an easy-to-use in-game world editor for Minecraft", + url = "https://enginehub.org/worldedit/") public class SpongeWorldEdit { + @Inject + private Logger logger; + + private Metrics2 metrics; + public static final String MOD_ID = "worldedit"; private static final int BSTATS_PLUGIN_ID = 3329; + private SpongePermissionsProvider provider; + + @Inject + private PluginContainer container; + private static SpongeWorldEdit inst; + public static PluginContainer container() { + return inst.container; + } + public static SpongeWorldEdit inst() { return inst; } - private final Logger logger; - private final PluginContainer container; - private final SpongeConfiguration config; - private final Path workingDir; - - private SpongePermissionsProvider provider; private SpongePlatform platform; + private SpongeImplAdapter spongeAdapter; + + @Inject + private SpongeConfiguration config; + + @Inject @ConfigDir(sharedRoot = false) + private File workingDir; @Inject - public SpongeWorldEdit(Logger logger, - PluginContainer container, - SpongeConfiguration config, - Metrics.Factory metricsFactory, - @ConfigDir(sharedRoot = false) - Path workingDir) { - this.logger = logger; - this.container = container; - this.config = config; - this.workingDir = workingDir; - metricsFactory.make(BSTATS_PLUGIN_ID); + public SpongeWorldEdit(Metrics2.Factory metricsFactory) { inst = this; + metrics = metricsFactory.make(BSTATS_PLUGIN_ID); } @Listener - public void onPluginConstruction(ConstructPluginEvent event) { - this.platform = new SpongePlatform(this); - - WorldEdit.getInstance().getPlatformManager().register(platform); - - this.provider = new SpongePermissionsProvider(); + public void preInit(GamePreInitializationEvent event) { + // Load configuration + config.load(); - event.game().eventManager().registerListeners( - container, - new CUIChannelHandler.RegistrationHandler(), - MethodHandles.lookup() - ); + Task.builder().interval(30, TimeUnit.SECONDS).execute(ThreadSafeCache.getInstance()).submit(this); + } - event.game().eventManager().registerListeners( - container, - new SpongeWorldEditListener(this), - MethodHandles.lookup() - ); + @Listener + public void init(GameInitializationEvent event) { + CUIChannelHandler.init(); + } + @Listener + public void postInit(GamePostInitializationEvent event) { logger.info("WorldEdit for Sponge (version " + getInternalVersion() + ") is loaded"); } @Listener - public void serverStarting(StartingEngineEvent event) { - final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME); + public void serverAboutToStart(GameAboutToStartServerEvent event) { + if (this.platform != null) { + logger.warn("GameAboutToStartServerEvent occurred when GameStoppingServerEvent hasn't"); + WorldEdit.getInstance().getPlatformManager().unregister(platform); + } + + final Path delChunks = workingDir.toPath().resolve(DELCHUNKS_FILE_NAME); if (Files.exists(delChunks)) { ChunkDeleter.runFromFile(delChunks, true); } - } - @Listener - public void serverStarted(StartedEngineEvent event) { - event.engine().scheduler().submit(Task.builder() - .plugin(container) - .interval(30, TimeUnit.SECONDS) - .execute(ThreadSafeCache.getInstance()) - .build()); - - event.game().registry(RegistryTypes.BLOCK_TYPE).streamEntries().forEach(blockType -> { - String id = blockType.key().asString(); + this.platform = new SpongePlatform(this); + this.provider = new SpongePermissionsProvider(); + + for (BlockType blockType : Sponge.getRegistry().getAllOf(BlockType.class)) { + // TODO Handle blockstate stuff + String id = blockType.getId(); if (!com.sk89q.worldedit.world.block.BlockType.REGISTRY.keySet().contains(id)) { - com.sk89q.worldedit.world.block.BlockType.REGISTRY.register(id, new com.sk89q.worldedit.world.block.BlockType( - id, - input -> { - BlockType spongeBlockType = Sponge.game().registry(RegistryTypes.BLOCK_TYPE).value( - ResourceKey.resolve(input.getBlockType().id()) - ); - return SpongeAdapter.adapt(spongeBlockType.defaultState()); - } - )); + com.sk89q.worldedit.world.block.BlockType.REGISTRY.register(id, new com.sk89q.worldedit.world.block.BlockType(id)); } - }); + } - event.game().registry(RegistryTypes.ITEM_TYPE).streamEntries().forEach(itemType -> { - String id = itemType.key().asString(); + for (ItemType itemType : Sponge.getRegistry().getAllOf(ItemType.class)) { + String id = itemType.getId(); if (!com.sk89q.worldedit.world.item.ItemType.REGISTRY.keySet().contains(id)) { com.sk89q.worldedit.world.item.ItemType.REGISTRY.register(id, new com.sk89q.worldedit.world.item.ItemType(id)); } - }); - - event.game().registry(RegistryTypes.ENTITY_TYPE).streamEntries().forEach(entityType -> { - String id = entityType.key().asString(); - if (!com.sk89q.worldedit.world.entity.EntityType.REGISTRY.keySet().contains(id)) { - com.sk89q.worldedit.world.entity.EntityType.REGISTRY.register(id, new com.sk89q.worldedit.world.entity.EntityType(id)); - } - }); - - for (ServerWorld world : event.engine().worldManager().worlds()) { - world.registry(RegistryTypes.BIOME).streamEntries().forEach(biomeType -> { - String id = biomeType.key().asString(); - if (!BiomeType.REGISTRY.keySet().contains(id)) { - BiomeType.REGISTRY.register(id, new BiomeType(id)); - } - }); } - // Disabled until https://github.com/SpongePowered/SpongeAPI/issues/2520 is resolved - // Will also need implementations in SpongeWorld to do placement - // Sponge.server().registry(RegistryTypes.FEATURE).streamEntries().forEach(feature -> { - // String id = feature.key().asString(); - // if (!ConfiguredFeatureType.REGISTRY.keySet().contains(id)) { - // ConfiguredFeatureType.REGISTRY.register(id, new ConfiguredFeatureType(id)); - // } - // }); - // Sponge.server().registry(RegistryTypes.STRUCTURE).streamEntries().forEach(structure -> { - // String id = structure.key().asString(); - // if (!StructureType.REGISTRY.keySet().contains(id)) { - // StructureType.REGISTRY.register(id, new StructureType(id)); - // } - // }); - - event.game().registry(RegistryTypes.PLACED_FEATURE).streamEntries().forEach(feature -> { - String id = feature.key().asString(); - var underlyingFeatureType = feature.value().feature().type(); - if (underlyingFeatureType.equals(FeatureTypes.TREE) || underlyingFeatureType.equals(FeatureTypes.FALLEN_TREE) || underlyingFeatureType.equals(FeatureTypes.CORAL_TREE)) { - if (!TreeType.REGISTRY.keySet().contains(id)) { - TreeType.REGISTRY.register(id, new TreeType(id)); - } - } - }); - event.game().registry(RegistryTypes.BLOCK_TYPE).tags().forEach(blockTypeTag -> { - String id = blockTypeTag.key().asString(); - if (!BlockCategory.REGISTRY.keySet().contains(id)) { - BlockCategory.REGISTRY.register(id, new BlockCategory(id)); - } - }); - event.game().registry(RegistryTypes.ITEM_TYPE).tags().forEach(itemTypeTag -> { - String id = itemTypeTag.key().asString(); - if (!ItemCategory.REGISTRY.keySet().contains(id)) { - ItemCategory.REGISTRY.register(id, new ItemCategory(id)); - } - }); - event.game().registry(RegistryTypes.BIOME).tags().forEach(biomeTag -> { - String id = biomeTag.key().asString(); - if (!BiomeCategory.REGISTRY.keySet().contains(id)) { - BiomeCategory.REGISTRY.register(id, new BiomeCategory(id, () -> event.game().registry(RegistryTypes.BIOME).taggedValues(biomeTag).map(SpongeAdapter::adapt).collect(Collectors.toSet()))); - } - }); - - Registries.get(""); - - config.load(); - WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); + WorldEdit.getInstance().getPlatformManager().register(platform); } @Listener - public void serverStopping(StoppingEngineEvent event) { + public void serverStopping(GameStoppingServerEvent event) { WorldEdit worldEdit = WorldEdit.getInstance(); worldEdit.getSessionManager().unload(); - WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); + worldEdit.getPlatformManager().unregister(platform); } @Listener - public void registerCommand(RegisterCommandEvent event) { - WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); - PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); - Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); - if (commandsPlatform != platform || !platform.isHookingEvents()) { - // We're not in control of commands/events -- do not register. - return; - } + public void serverStarted(GameStartedServerEvent event) { + WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); + + loadAdapter(); + } + + private void loadAdapter() { + WorldEdit worldEdit = WorldEdit.getInstance(); + + // Attempt to load a Sponge adapter + SpongeImplLoader adapterLoader = new SpongeImplLoader(); - List commands = manager.getPlatformCommandManager().getCommandManager() - .getAllCommands().toList(); - for (org.enginehub.piston.Command command : commands) { - registerAdaptedCommand(event, command); + try { + adapterLoader.addFromPath(getClass().getClassLoader()); + } catch (IOException e) { + logger.warn("Failed to search path for Sponge adapters"); + } - Set perms = command.getCondition().as(PermissionCondition.class) - .map(PermissionCondition::getPermissions) - .orElseGet(Collections::emptySet); - if (!perms.isEmpty()) { - perms.forEach(getPermissionsProvider()::registerPermission); + try { + adapterLoader.addFromJar(container.getSource().get().toFile()); + } catch (IOException e) { + logger.warn("Failed to search " + container.getSource().get().toFile() + " for Sponge adapters", e); + } + try { + spongeAdapter = adapterLoader.loadAdapter(); + logger.info("Using " + spongeAdapter.getClass().getCanonicalName() + " as the Sponge adapter"); + } catch (AdapterLoadException e) { + Platform platform = worldEdit.getPlatformManager().queryCapability(Capability.WORLD_EDITING); + if (platform instanceof SpongePlatform) { + logger.warn(e.getMessage()); + } else { + logger.info("WorldEdit could not find a Sponge adapter for this MC version, " + + "but it seems that you have another implementation of WorldEdit installed (" + platform.getPlatformName() + ") " + + "that handles the world editing."); } } } - private String rebuildArguments(String commandLabel, String args) { - int plSep = commandLabel.indexOf(':'); - if (plSep >= 0 && plSep < commandLabel.length() + 1) { - commandLabel = commandLabel.substring(plSep + 1); + public SpongeImplAdapter getAdapter() { + return this.spongeAdapter; + } + + @Listener + public void onPlayerItemInteract(InteractItemEvent.Secondary event, @Root Player spongePlayer) { + if (platform == null) { + return; } - StringBuilder sb = new StringBuilder("/").append(commandLabel); + if (!platform.isHookingEvents()) return; // We have to be told to catch these events + + WorldEdit we = WorldEdit.getInstance(); - String[] split = args.split(" ", -1); - if (split.length > 0) { - sb.append(" "); + SpongePlayer player = wrapPlayer(spongePlayer); + if (we.handleRightClick(player)) { + event.setCancelled(true); } - return Joiner.on(" ").appendTo(sb, split).toString(); } - private void registerAdaptedCommand(RegisterCommandEvent event, org.enginehub.piston.Command command) { - CommandAdapter adapter = new CommandAdapter(command) { - @Override - public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { - CommandEvent weEvent = new CommandEvent(SpongeWorldEdit.inst().wrapCommandCause(cause), rebuildArguments(command.getName(), arguments.remaining()).trim()); - WorldEdit.getInstance().getEventBus().post(weEvent); - return weEvent.isCancelled() ? CommandResult.success() : CommandResult.builder().build(); + @Listener + public void onPlayerInteract(InteractBlockEvent event, @Root Player spongePlayer) { + if (platform == null) { + return; + } + + if (!platform.isHookingEvents()) return; // We have to be told to catch these events + + WorldEdit we = WorldEdit.getInstance(); + + SpongePlayer player = wrapPlayer(spongePlayer); + com.sk89q.worldedit.world.World world = player.getWorld(); + + BlockSnapshot targetBlock = event.getTargetBlock(); + Optional> optLoc = targetBlock.getLocation(); + + BlockType interactedType = targetBlock.getState().getType(); + if (event instanceof InteractBlockEvent.Primary) { + if (interactedType != BlockTypes.AIR) { + if (!optLoc.isPresent()) { + return; + } + + Location loc = optLoc.get(); + com.sk89q.worldedit.util.Location pos = new com.sk89q.worldedit.util.Location( + world, loc.getX(), loc.getY(), loc.getZ()); + + if (we.handleBlockLeftClick(player, pos)) { + event.setCancelled(true); + } + + if (we.handleArmSwing(player)) { + event.setCancelled(true); + } + } else { + if (we.handleArmSwing(player)) { + event.setCancelled(true); + } + } + } else if (event instanceof InteractBlockEvent.Secondary) { + if (!optLoc.isPresent()) { + return; + } + + Location loc = optLoc.get(); + com.sk89q.worldedit.util.Location pos = new com.sk89q.worldedit.util.Location( + world, loc.getX(), loc.getY(), loc.getZ()); + + if (we.handleBlockRightClick(player, pos)) { + event.setCancelled(true); } - @Override - public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { - String args = rebuildArguments(command.getName(), arguments.remaining()); - CommandSuggestionEvent weEvent = new CommandSuggestionEvent(SpongeWorldEdit.inst().wrapCommandCause(cause), args); - WorldEdit.getInstance().getEventBus().post(weEvent); - return CommandUtil.fixSuggestions(args, weEvent.getSuggestions()) - .stream().map(CommandCompletion::of).collect(toList()); + if (we.handleRightClick(player)) { + event.setCancelled(true); } - }; - event.register( - container, adapter, command.getName(), command.getAliases().toArray(new String[0]) - ); + } } - public PluginContainer getPluginContainer() { - return container; + @Listener + public void onPlayerQuit(ClientConnectionEvent.Disconnect event) { + WorldEdit.getInstance().getEventBus() + .post(new SessionIdleEvent(new SpongePlayer.SessionKeyImpl(event.getTargetEntity()))); + } + + public static ItemStack toSpongeItemStack(BaseItemStack item) { + return inst().getAdapter().makeSpongeStack(item); } /** @@ -330,27 +311,46 @@ SpongeConfiguration getConfig() { return this.config; } - public Actor wrapCommandCause(CommandCause cause) { - Object rootCause = cause.root(); - if (rootCause instanceof ServerPlayer serverPlayer) { - return SpongeAdapter.adapt(serverPlayer); - } - if (rootCause instanceof LocatableBlock locatableBlock) { - Optional optionalBlockEntity = locatableBlock.world().blockEntity(locatableBlock.blockPosition()); - if (optionalBlockEntity.isPresent()) { - BlockEntity blockEntity = optionalBlockEntity.get(); - if (blockEntity instanceof CommandBlock commandBlock) { - return new SpongeBlockCommandSender(this, commandBlock); - } - } - } - if (rootCause instanceof Audience audience) { - return new SpongeCommandSender(audience); + /** + * Get the WorldEdit proxy for the given player. + * + * @param player the player + * @return the WorldEdit player + */ + public SpongePlayer wrapPlayer(Player player) { + checkNotNull(player); + return new SpongePlayer(platform, player); + } + + public Actor wrapCommandSource(CommandSource sender) { + if (sender instanceof Player) { + return wrapPlayer((Player) sender); } - throw new UnsupportedOperationException("Cannot wrap " + rootCause.getClass()); + return new SpongeCommandSender(this, sender); } + /** + * Get the session for a player. + * + * @param player the player + * @return the session + */ + public LocalSession getSession(Player player) { + checkNotNull(player); + return WorldEdit.getInstance().getSessionManager().get(wrapPlayer(player)); + } + + /** + * Get the WorldEdit proxy for the given world. + * + * @param world the world + * @return the WorldEdit world + */ + public SpongeWorld getWorld(World world) { + checkNotNull(world); + return getAdapter().getWorld(world); + } /** * Get the WorldEdit proxy for the platform. @@ -361,16 +361,12 @@ public Platform getPlatform() { return this.platform; } - SpongePlatform getInternalPlatform() { - return this.platform; - } - /** * Get the working directory where WorldEdit's files are stored. * * @return the working directory */ - public Path getWorkingDir() { + public File getWorkingDir() { return this.workingDir; } @@ -380,7 +376,7 @@ public Path getWorkingDir() { * @return a version string */ String getInternalVersion() { - return container.metadata().version().toString(); + return container.getVersion().orElse("Unknown"); } public void setPermissionsProvider(SpongePermissionsProvider provider) { @@ -390,4 +386,5 @@ public void setPermissionsProvider(SpongePermissionsProvider provider) { public SpongePermissionsProvider getPermissionsProvider() { return provider; } + }