From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: dfsek <dfsek@protonmail.com>
Date: Sat, 19 Jun 2021 20:15:59 -0700
Subject: [PATCH] Add Feature Generation API


diff --git a/src/main/java/io/papermc/paper/world/generation/CraftProtoWorld.java b/src/main/java/io/papermc/paper/world/generation/CraftProtoWorld.java
new file mode 100644
index 0000000000000000000000000000000000000000..69b17fe56f28f8978abc3f363a9e805d7c7b007a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/world/generation/CraftProtoWorld.java
@@ -0,0 +1,115 @@
+package io.papermc.paper.world.generation;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.WorldGenRegion;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.MobSpawnType;
+import net.minecraft.world.entity.SpawnGroupData;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import org.bukkit.World;
+import org.bukkit.block.BlockState;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.craftbukkit.block.CraftBlockEntityState;
+import org.bukkit.craftbukkit.block.CraftBlockState;
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
+import org.bukkit.craftbukkit.inventory.CraftMetaBlockState;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class CraftProtoWorld implements ProtoWorld {
+    private WorldGenRegion region;
+
+    public CraftProtoWorld(WorldGenRegion region) {
+        this.region = region;
+    }
+
+    public void clearReference() {
+        region = null;
+    }
+
+    @Override
+    public void setBlockData(int x, int y, int z, @NotNull BlockData data) {
+        BlockPos position = new BlockPos(x, y, z);
+        getDelegate().setBlock(position, ((CraftBlockData) data).getState(), 3);
+    }
+
+    @Override
+    public void setBlockState(int x, int y, int z, @NotNull BlockState state) {
+        BlockPos pos = new BlockPos(x, y, z);
+        if(!state.getBlockData().matches(getDelegate().getBlockState(pos).createCraftBlockData())) {
+            throw new IllegalArgumentException("BlockData does not match! Expected " + state.getBlockData().getAsString(false) + ", got " + getDelegate().getBlockState(pos).createCraftBlockData().getAsString(false));
+        }
+        getDelegate().getBlockEntity(pos).load(((CraftBlockEntityState<?>) state).getSnapshotNBT());
+    }
+
+    @Override
+    public @NotNull BlockState getBlockState(int x, int y, int z) {
+        BlockEntity entity = getDelegate().getBlockEntity(new BlockPos(x, y, z));
+        return CraftMetaBlockState.createBlockState(entity.getBlockState().getBukkitMaterial(), entity.save(new CompoundTag()));
+    }
+
+    @Override
+    public void scheduleBlockUpdate(int x, int y, int z) {
+        BlockPos position = new BlockPos(x, y, z);
+        getDelegate().getBlockTicks().scheduleTick(position, getDelegate().getBlockIfLoaded(position), 0);
+    }
+
+    @Override
+    public void scheduleFluidUpdate(int x, int y, int z) {
+        BlockPos position = new BlockPos(x, y, z);
+        getDelegate().getLiquidTicks().scheduleTick(position, getDelegate().getFluidState(position).getType(), 0);
+    }
+
+    @Override
+    public @NotNull World getWorld() {
+        // reading/writing the returned Minecraft world causes a deadlock.
+        // By implementing this, and covering it in warnings, we're assuming people won't be stupid, and
+        // if they are stupid, they'll figure it out pretty fast.
+        return getDelegate().getMinecraftWorld().getWorld();
+    }
+
+    @Override
+    public @NotNull BlockData getBlockData(int x, int y, int z) {
+        return CraftBlockData.fromData(getDelegate().getBlockState(new BlockPos(x, y, z)));
+    }
+
+    @Override
+    public int getCenterChunkX() {
+        return getDelegate().getCenter().x;
+    }
+
+    @Override
+    public int getCenterChunkZ() {
+        return getDelegate().getCenter().z;
+    }
+
+    @SuppressWarnings({"unchecked", "deprecation"})
+    @Override
+    public <T extends Entity> @NotNull T spawn(@NotNull Vector location, @NotNull Class<T> clazz, @Nullable Consumer<T> function, CreatureSpawnEvent.@NotNull SpawnReason reason) throws IllegalArgumentException {
+        net.minecraft.world.entity.Entity entity = getDelegate().getMinecraftWorld().getWorld().createEntity(location.toLocation(getWorld()), clazz);
+        Objects.requireNonNull(entity, "Cannot spawn null entity");
+        if (entity instanceof Mob) {
+            ((Mob) entity).finalizeSpawn(getDelegate(), getDelegate().getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.COMMAND, (SpawnGroupData) null, null);
+        }
+
+        if (function != null) {
+            function.accept((T) entity.getBukkitEntity());
+        }
+
+        getDelegate().addEntity(entity, reason);
+        return (T) entity.getBukkitEntity();
+    }
+
+    @NotNull
+    private WorldGenRegion getDelegate() {
+        return Objects.requireNonNull(region, "Cannot access ProtoWorld after generation!");
+    }
+}
+
diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
index 98461949badc8a708c1e7e2f225a93e8d1ca5822..4fa8cce0818a8e461330fc29021e6ff65cb67125 100644
--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
@@ -187,6 +187,12 @@ public class CustomChunkGenerator extends InternalChunkGenerator {
         for (BlockPos lightPosition : craftData.getLights()) {
             ((ProtoChunk) chunk).addLight(new BlockPos((x << 4) + lightPosition.getX(), lightPosition.getY(), (z << 4) + lightPosition.getZ())); // PAIL rename addLightBlock
         }
+
+        // Paper start
+        io.papermc.paper.world.generation.CraftProtoWorld protoWorld = new io.papermc.paper.world.generation.CraftProtoWorld(region);
+        generator.generateDecorations(protoWorld);
+        protoWorld.clearReference(); // make sure people dont try to use the ProtoWorld after we're done with it.
+        // Paper end
     }
 
     @Override
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java
index bfdeb4f2971e65c38334f2f90c66ef62564e83e6..5ec7a3903838ce2f60926782965107e84b44643c 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java
@@ -271,13 +271,18 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
         return this.blockEntityTag != null;
     }
 
+    // Paper start - Delegate to utility method
     @Override
     public BlockState getBlockState() {
-        Material stateMaterial = (this.material != Material.SHIELD) ? this.material : CraftMetaBlockState.shieldToBannerHack(this.blockEntityTag); // Only actually used for jigsaws
-        if (this.blockEntityTag != null) {
-            switch (this.material) {
+        return createBlockState(this.material, this.blockEntityTag);
+    }
+
+    public static BlockState createBlockState(Material material, CompoundTag blockEntityTag) {
+        Material stateMaterial = (material != Material.SHIELD) ? material : CraftMetaBlockState.shieldToBannerHack(blockEntityTag); // Only actually used for jigsaws
+        if (blockEntityTag != null) {
+            switch (material) {
                 case SHIELD:
-                    this.blockEntityTag.putString("id", "banner");
+                    blockEntityTag.putString("id", "banner");
                     break;
                 case SHULKER_BOX:
                 case WHITE_SHULKER_BOX:
@@ -296,19 +301,19 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
                 case GREEN_SHULKER_BOX:
                 case RED_SHULKER_BOX:
                 case BLACK_SHULKER_BOX:
-                    this.blockEntityTag.putString("id", "shulker_box");
+                    blockEntityTag.putString("id", "shulker_box");
                     break;
                 case BEE_NEST:
                 case BEEHIVE:
-                    this.blockEntityTag.putString("id", "beehive");
+                    blockEntityTag.putString("id", "beehive");
                     break;
             }
         }
         BlockPos blockposition = BlockPos.ZERO;
         net.minecraft.world.level.block.state.BlockState iblockdata = CraftMagicNumbers.getBlock(stateMaterial).defaultBlockState();
-        BlockEntity te = (this.blockEntityTag == null) ? null : BlockEntity.loadStatic(blockposition, iblockdata, blockEntityTag);
+        BlockEntity te = (blockEntityTag == null) ? null : BlockEntity.loadStatic(blockposition, iblockdata, blockEntityTag);
 
-        switch (this.material) {
+        switch (material) {
         case ACACIA_SIGN:
         case ACACIA_WALL_SIGN:
         case BIRCH_SIGN:
@@ -328,53 +333,53 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
             if (te == null) {
                 te = new SignBlockEntity(blockposition, iblockdata);
             }
-            return new CraftSign(this.material, (SignBlockEntity) te);
+            return new CraftSign(material, (SignBlockEntity) te);
         case CHEST:
         case TRAPPED_CHEST:
             if (te == null) {
                 te = new ChestBlockEntity(blockposition, iblockdata);
             }
-            return new CraftChest(this.material, (ChestBlockEntity) te);
+            return new CraftChest(material, (ChestBlockEntity) te);
         case FURNACE:
             if (te == null) {
                 te = new FurnaceBlockEntity(blockposition, iblockdata);
             }
-            return new CraftFurnaceFurnace(this.material, (FurnaceBlockEntity) te);
+            return new CraftFurnaceFurnace(material, (FurnaceBlockEntity) te);
         case DISPENSER:
             if (te == null) {
                 te = new DispenserBlockEntity(blockposition, iblockdata);
             }
-            return new CraftDispenser(this.material, (DispenserBlockEntity) te);
+            return new CraftDispenser(material, (DispenserBlockEntity) te);
         case DROPPER:
             if (te == null) {
                 te = new DropperBlockEntity(blockposition, iblockdata);
             }
-            return new CraftDropper(this.material, (DropperBlockEntity) te);
+            return new CraftDropper(material, (DropperBlockEntity) te);
         case END_GATEWAY:
             if (te == null) {
                 te = new TheEndGatewayBlockEntity(blockposition, iblockdata);
             }
-            return new CraftEndGateway(this.material, (TheEndGatewayBlockEntity) te);
+            return new CraftEndGateway(material, (TheEndGatewayBlockEntity) te);
         case HOPPER:
             if (te == null) {
                 te = new HopperBlockEntity(blockposition, iblockdata);
             }
-            return new CraftHopper(this.material, (HopperBlockEntity) te);
+            return new CraftHopper(material, (HopperBlockEntity) te);
         case SPAWNER:
             if (te == null) {
                 te = new SpawnerBlockEntity(blockposition, iblockdata);
             }
-            return new CraftCreatureSpawner(this.material, (SpawnerBlockEntity) te);
+            return new CraftCreatureSpawner(material, (SpawnerBlockEntity) te);
         case JUKEBOX:
             if (te == null) {
                 te = new JukeboxBlockEntity(blockposition, iblockdata);
             }
-            return new CraftJukebox(this.material, (JukeboxBlockEntity) te);
+            return new CraftJukebox(material, (JukeboxBlockEntity) te);
         case BREWING_STAND:
             if (te == null) {
                 te = new BrewingStandBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBrewingStand(this.material, (BrewingStandBlockEntity) te);
+            return new CraftBrewingStand(material, (BrewingStandBlockEntity) te);
         case CREEPER_HEAD:
         case CREEPER_WALL_HEAD:
         case DRAGON_HEAD:
@@ -390,24 +395,24 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
             if (te == null) {
                 te = new SkullBlockEntity(blockposition, iblockdata);
             }
-            return new CraftSkull(this.material, (SkullBlockEntity) te);
+            return new CraftSkull(material, (SkullBlockEntity) te);
         case COMMAND_BLOCK:
         case REPEATING_COMMAND_BLOCK:
         case CHAIN_COMMAND_BLOCK:
             if (te == null) {
                 te = new CommandBlockEntity(blockposition, iblockdata);
             }
-            return new CraftCommandBlock(this.material, (CommandBlockEntity) te);
+            return new CraftCommandBlock(material, (CommandBlockEntity) te);
         case BEACON:
             if (te == null) {
                 te = new BeaconBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBeacon(this.material, (BeaconBlockEntity) te);
+            return new CraftBeacon(material, (BeaconBlockEntity) te);
         case SHIELD:
             if (te == null) {
                 te = new BannerBlockEntity(blockposition, iblockdata);
             }
-            ((BannerBlockEntity) te).baseColor = (this.blockEntityTag == null) ? DyeColor.WHITE : DyeColor.byId(this.blockEntityTag.getInt(CraftMetaBanner.BASE.NBT));
+            ((BannerBlockEntity) te).baseColor = (blockEntityTag == null) ? DyeColor.WHITE : DyeColor.byId(blockEntityTag.getInt(CraftMetaBanner.BASE.NBT));
         case BLACK_BANNER:
         case BLACK_WALL_BANNER:
         case BLUE_BANNER:
@@ -443,12 +448,12 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
             if (te == null) {
                 te = new BannerBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBanner(this.material == Material.SHIELD ? CraftMetaBlockState.shieldToBannerHack(this.blockEntityTag) : this.material, (BannerBlockEntity) te);
+            return new CraftBanner(material == Material.SHIELD ? CraftMetaBlockState.shieldToBannerHack(blockEntityTag) : material, (BannerBlockEntity) te);
         case STRUCTURE_BLOCK:
             if (te == null) {
                 te = new StructureBlockEntity(blockposition, iblockdata);
             }
-            return new CraftStructureBlock(this.material, (StructureBlockEntity) te);
+            return new CraftStructureBlock(material, (StructureBlockEntity) te);
         case SHULKER_BOX:
         case WHITE_SHULKER_BOX:
         case ORANGE_SHULKER_BOX:
@@ -469,78 +474,79 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
             if (te == null) {
                 te = new ShulkerBoxBlockEntity(blockposition, iblockdata);
             }
-            return new CraftShulkerBox(this.material, (ShulkerBoxBlockEntity) te);
+            return new CraftShulkerBox(material, (ShulkerBoxBlockEntity) te);
         case ENCHANTING_TABLE:
             if (te == null) {
                 te = new EnchantmentTableBlockEntity(blockposition, iblockdata);
             }
-            return new CraftEnchantingTable(this.material, (EnchantmentTableBlockEntity) te);
+            return new CraftEnchantingTable(material, (EnchantmentTableBlockEntity) te);
         case ENDER_CHEST:
             if (te == null) {
                 te = new EnderChestBlockEntity(blockposition, iblockdata);
             }
-            return new CraftEnderChest(this.material, (EnderChestBlockEntity) te);
+            return new CraftEnderChest(material, (EnderChestBlockEntity) te);
         case DAYLIGHT_DETECTOR:
             if (te == null) {
                 te = new DaylightDetectorBlockEntity(blockposition, iblockdata);
             }
-            return new CraftDaylightDetector(this.material, (DaylightDetectorBlockEntity) te);
+            return new CraftDaylightDetector(material, (DaylightDetectorBlockEntity) te);
         case COMPARATOR:
             if (te == null) {
                 te = new ComparatorBlockEntity(blockposition, iblockdata);
             }
-            return new CraftComparator(this.material, (ComparatorBlockEntity) te);
+            return new CraftComparator(material, (ComparatorBlockEntity) te);
         case BARREL:
             if (te == null) {
                 te = new BarrelBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBarrel(this.material, (BarrelBlockEntity) te);
+            return new CraftBarrel(material, (BarrelBlockEntity) te);
         case BELL:
             if (te == null) {
                 te = new BellBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBell(this.material, (BellBlockEntity) te);
+            return new CraftBell(material, (BellBlockEntity) te);
         case BLAST_FURNACE:
             if (te == null) {
                 te = new BlastFurnaceBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBlastFurnace(this.material, (BlastFurnaceBlockEntity) te);
+            return new CraftBlastFurnace(material, (BlastFurnaceBlockEntity) te);
         case CAMPFIRE:
         case SOUL_CAMPFIRE:
             if (te == null) {
                 te = new CampfireBlockEntity(blockposition, iblockdata);
             }
-            return new CraftCampfire(this.material, (CampfireBlockEntity) te);
+            return new CraftCampfire(material, (CampfireBlockEntity) te);
         case JIGSAW:
             if (te == null) {
                 te = new JigsawBlockEntity(blockposition, iblockdata);
             }
-            return new CraftJigsaw(this.material, (JigsawBlockEntity) te);
+            return new CraftJigsaw(material, (JigsawBlockEntity) te);
         case LECTERN:
             if (te == null) {
                 te = new LecternBlockEntity(blockposition, iblockdata);
             }
-            return new CraftLectern(this.material, (LecternBlockEntity) te);
+            return new CraftLectern(material, (LecternBlockEntity) te);
         case SMOKER:
             if (te == null) {
                 te = new SmokerBlockEntity(blockposition, iblockdata);
             }
-            return new CraftSmoker(this.material, (SmokerBlockEntity) te);
+            return new CraftSmoker(material, (SmokerBlockEntity) te);
         case BEE_NEST:
         case BEEHIVE:
             if (te == null) {
                 te = new BeehiveBlockEntity(blockposition, iblockdata);
             }
-            return new CraftBeehive(this.material, (BeehiveBlockEntity) te);
+            return new CraftBeehive(material, (BeehiveBlockEntity) te);
         case SCULK_SENSOR:
             if (te == null) {
                 te = new SculkSensorBlockEntity(blockposition, iblockdata);
             }
-            return new CraftSculkSensor(this.material, (SculkSensorBlockEntity) te);
+            return new CraftSculkSensor(material, (SculkSensorBlockEntity) te);
         default:
-            throw new IllegalStateException("Missing blockState for " + this.material);
+            throw new IllegalStateException("Missing blockState for " + material);
         }
     }
+    // Paper end
 
     @Override
     public void setBlockState(BlockState blockState) {
