diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java
index ac215b83e..ee6249eb5 100644
--- a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java
+++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/BukkitAddon.java
@@ -5,8 +5,10 @@ import ca.solostudios.strata.version.Version;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent;
+import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.world.biome.Biome;
+import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions;
import com.dfsek.terra.bukkit.config.VanillaBiomeProperties;
@@ -21,6 +23,12 @@ public class BukkitAddon implements BaseAddon {
@Override
public void initialize() {
+ terraBukkitPlugin.getEventManager()
+ .getHandler(FunctionalEventHandler.class)
+ .register(this, ConfigPackPreLoadEvent.class)
+ .then(event -> event.getPack().getContext().put(event.loadTemplate(new PreLoadCompatibilityOptions())))
+ .global();
+
terraBukkitPlugin.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(this, ConfigurationLoadEvent.class)
diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/config/PreLoadCompatibilityOptions.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/config/PreLoadCompatibilityOptions.java
new file mode 100644
index 000000000..c9c4de3f4
--- /dev/null
+++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/config/PreLoadCompatibilityOptions.java
@@ -0,0 +1,60 @@
+/*
+ * This file is part of Terra.
+ *
+ * Terra 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.
+ *
+ * Terra 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 Terra. If not, see .
+ */
+
+package com.dfsek.terra.bukkit.config;
+
+import com.dfsek.tectonic.api.config.template.ConfigTemplate;
+import com.dfsek.tectonic.api.config.template.annotations.Default;
+import com.dfsek.tectonic.api.config.template.annotations.Value;
+
+import com.dfsek.terra.api.properties.Properties;
+
+
+@SuppressWarnings("FieldMayBeFinal")
+public class PreLoadCompatibilityOptions implements ConfigTemplate, Properties {
+ @Value("minecraft.use-vanilla-biomes")
+ @Default
+ private boolean vanillaBiomes = false;
+
+ @Value("minecraft.beard.enable")
+ @Default
+ private boolean beard = true;
+
+ @Value("minecraft.beard.threshold")
+ @Default
+ private double beardThreshold = 0.5;
+
+ @Value("minecraft.beard.air-threshold")
+ @Default
+ private double airThreshold = -0.5;
+
+ public boolean useVanillaBiomes() {
+ return vanillaBiomes;
+ }
+
+ public boolean isBeard() {
+ return beard;
+ }
+
+ public double getBeardThreshold() {
+ return beardThreshold;
+ }
+
+ public double getAirThreshold() {
+ return airThreshold;
+ }
+}
diff --git a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java
index 81933be0f..a0ba7e718 100644
--- a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java
+++ b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/NMSChunkGeneratorDelegate.java
@@ -1,5 +1,9 @@
package com.dfsek.terra.bukkit.nms.v1_19_R1;
+import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions;
+
+import com.dfsek.terra.bukkit.world.BukkitWorldProperties;
+
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
@@ -10,14 +14,21 @@ import net.minecraft.core.SectionPos;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
+import net.minecraft.world.level.levelgen.Beardifier;
+import net.minecraft.world.level.levelgen.DensityFunction;
+import net.minecraft.world.level.levelgen.DensityFunction.FunctionContext;
+import net.minecraft.world.level.levelgen.DensityFunction.SinglePointContext;
import net.minecraft.world.level.levelgen.GenerationStep.Carving;
import net.minecraft.world.level.levelgen.Heightmap.Types;
import net.minecraft.world.level.levelgen.RandomState;
@@ -103,7 +114,43 @@ public class NMSChunkGeneratorDelegate extends ChunkGenerator {
public @NotNull CompletableFuture fillFromNoise(@NotNull Executor executor, @NotNull Blender blender,
@NotNull RandomState noiseConfig,
@NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk) {
- return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk);
+ return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk)
+ .thenApply(c -> {
+ LevelAccessor level = Reflection.STRUCTURE_MANAGER.getLevel(structureAccessor);
+ BiomeProvider biomeProvider = pack.getBiomeProvider();
+ PreLoadCompatibilityOptions compatibilityOptions = pack.getContext().get(PreLoadCompatibilityOptions.class);
+ if(compatibilityOptions.isBeard()) {
+ beard(structureAccessor, chunk, new BukkitWorldProperties(level.getMinecraftWorld().getWorld()), biomeProvider, compatibilityOptions);
+ }
+ return c;
+ });
+ }
+
+ private void beard(StructureManager structureAccessor, ChunkAccess chunk, WorldProperties world, BiomeProvider biomeProvider,
+ PreLoadCompatibilityOptions compatibilityOptions) {
+ Beardifier structureWeightSampler = Beardifier.forStructuresInChunk(structureAccessor, chunk.getPos());
+ double threshold = compatibilityOptions.getBeardThreshold();
+ double airThreshold = compatibilityOptions.getAirThreshold();
+ int xi = chunk.getPos().x << 4;
+ int zi = chunk.getPos().z << 4;
+ for(int x = 0; x < 16; x++) {
+ for(int z = 0; z < 16; z++) {
+ int depth = 0;
+ for(int y = world.getMaxHeight(); y >= world.getMinHeight(); y--) {
+ double noise = structureWeightSampler.compute(new SinglePointContext(x + xi, y, z + zi));
+ if(noise > threshold) {
+ chunk.setBlockState(new BlockPos(x, y, z), (BlockState) delegate
+ .getPalette(x + xi, y, z + zi, world, biomeProvider)
+ .get(depth, x + xi, y, z + zi, world.getSeed()), false);
+ depth++;
+ } else if(noise < airThreshold) {
+ chunk.setBlockState(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false);
+ } else {
+ depth = 0;
+ }
+ }
+ }
+ }
}
@Override
diff --git a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java
index 8fa9a0b01..b704952d5 100644
--- a/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java
+++ b/platforms/bukkit/nms/v1_19_R1/src/main/java/com/dfsek/terra/bukkit/nms/v1_19_R1/Reflection.java
@@ -1,14 +1,18 @@
package com.dfsek.terra.bukkit.nms.v1_19_R1;
import net.minecraft.core.MappedRegistry;
+import net.minecraft.world.level.LevelAccessor;
+import net.minecraft.world.level.StructureManager;
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory;
+import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter;
import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter;
import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies;
public class Reflection {
public static final MappedRegistryProxy MAPPED_REGISTRY;
+ public static final StructureManagerProxy STRUCTURE_MANAGER;
static {
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
@@ -16,6 +20,7 @@ public class Reflection {
Reflection.class.getClassLoader());
MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class);
+ STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class);
}
@@ -24,4 +29,10 @@ public class Reflection {
@FieldSetter("frozen")
void setFrozen(MappedRegistry> instance, boolean frozen);
}
+
+ @Proxies(StructureManager.class)
+ public interface StructureManagerProxy {
+ @FieldGetter("level")
+ LevelAccessor getLevel(StructureManager instance);
+ }
}