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); + } }