diff --git a/common/src/main/java/dev/worldgen/lithostitched/mixin/client/IntegratedServerMixin.java b/common/src/main/java/dev/worldgen/lithostitched/mixin/client/IntegratedServerMixin.java index 87a6bc2..87f07e3 100644 --- a/common/src/main/java/dev/worldgen/lithostitched/mixin/client/IntegratedServerMixin.java +++ b/common/src/main/java/dev/worldgen/lithostitched/mixin/client/IntegratedServerMixin.java @@ -1,7 +1,7 @@ package dev.worldgen.lithostitched.mixin.client; import dev.worldgen.lithostitched.worldgen.modifier.Modifier; -import dev.worldgen.lithostitched.worldgen.surface.SurfaceRuleManager; +import dev.worldgen.lithostitched.worldgen.surface.technical.SurfaceRuleManager; import net.minecraft.client.server.IntegratedServer; import net.minecraft.server.MinecraftServer; import org.spongepowered.asm.mixin.Mixin; diff --git a/common/src/main/java/dev/worldgen/lithostitched/mixin/common/ContextMixin.java b/common/src/main/java/dev/worldgen/lithostitched/mixin/common/ContextMixin.java new file mode 100644 index 0000000..baa6738 --- /dev/null +++ b/common/src/main/java/dev/worldgen/lithostitched/mixin/common/ContextMixin.java @@ -0,0 +1,91 @@ +package dev.worldgen.lithostitched.mixin.common; + +import dev.worldgen.lithostitched.worldgen.surface.conditions.LithostitchedSurfaceConditions; +import dev.worldgen.lithostitched.worldgen.surface.technical.IContextExtension; +import net.minecraft.core.*; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.levelgen.*; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.function.Function; + +@Mixin(SurfaceRules.Context.class) +public final class ContextMixin implements IContextExtension { + // Shadowed variables from Context + @Shadow + long lastUpdateXZ; + @Shadow + public int blockX; + @Shadow + public int blockZ; + @Shadow + @Final + public ChunkAccess chunk; + @Shadow + @Final + public RandomState randomState; + // Variables for the cached conditions + @Unique + @SuppressWarnings("all") + SurfaceRules.Condition cliff, flat, flatLiquid, aboveWater; + // Caches for heightmap, alongside its update timer + @Unique + @SuppressWarnings("all") + private int oceanHeightmapDepthCache = -Integer.MAX_VALUE; + @Unique + @SuppressWarnings("all") + private long lastUpdateHeightmapDepth; + + @Inject(method = "", at = @At("RETURN")) + public void instantiateConditions(SurfaceSystem system, + RandomState randomState, + ChunkAccess chunk, + NoiseChunk noiseChunk, + Function> biomeGetter, + Registry biomeRegistry, + WorldGenerationContext context, + CallbackInfo ci) { + SurfaceRules.Context self = (SurfaceRules.Context) (Object) this; + cliff = new LithostitchedSurfaceConditions.CliffCondition(self); + flat = new LithostitchedSurfaceConditions.FlatCondition(self); + flatLiquid = new LithostitchedSurfaceConditions.FlatLiquidCondition(self); + aboveWater = new LithostitchedSurfaceConditions.LandTopLayerCondition(self); + } + + @Override + public SurfaceRules.Condition lithostitched$getCliff() { + return cliff; + } + + @Override + public SurfaceRules.Condition lithostitched$getFlat() { + return flat; + } + + @Override + public SurfaceRules.Condition lithostitched$getFlatLiquid() { + return flatLiquid; + } + + @Override + public SurfaceRules.Condition lithostitched$getLandTopLayer() { + return aboveWater; + } + + @Override + public int lithostitched$getOceanHeightmapDepth() { + if (lastUpdateXZ != lastUpdateHeightmapDepth) { + oceanHeightmapDepthCache = chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, blockX, blockZ); + lastUpdateHeightmapDepth = lastUpdateXZ; + } + return oceanHeightmapDepthCache; + } +} + diff --git a/common/src/main/java/dev/worldgen/lithostitched/mixin/common/GameTestServerMixin.java b/common/src/main/java/dev/worldgen/lithostitched/mixin/common/GameTestServerMixin.java index 49b77c0..5fe1e6e 100644 --- a/common/src/main/java/dev/worldgen/lithostitched/mixin/common/GameTestServerMixin.java +++ b/common/src/main/java/dev/worldgen/lithostitched/mixin/common/GameTestServerMixin.java @@ -1,7 +1,7 @@ package dev.worldgen.lithostitched.mixin.common; import dev.worldgen.lithostitched.worldgen.modifier.Modifier; -import dev.worldgen.lithostitched.worldgen.surface.SurfaceRuleManager; +import dev.worldgen.lithostitched.worldgen.surface.technical.SurfaceRuleManager; import net.minecraft.gametest.framework.GameTestServer; import net.minecraft.server.MinecraftServer; import org.spongepowered.asm.mixin.Mixin; diff --git a/common/src/main/java/dev/worldgen/lithostitched/mixin/server/DedicatedServerMixin.java b/common/src/main/java/dev/worldgen/lithostitched/mixin/server/DedicatedServerMixin.java index cd0dbdf..4771500 100644 --- a/common/src/main/java/dev/worldgen/lithostitched/mixin/server/DedicatedServerMixin.java +++ b/common/src/main/java/dev/worldgen/lithostitched/mixin/server/DedicatedServerMixin.java @@ -1,7 +1,7 @@ package dev.worldgen.lithostitched.mixin.server; import dev.worldgen.lithostitched.worldgen.modifier.Modifier; -import dev.worldgen.lithostitched.worldgen.surface.SurfaceRuleManager; +import dev.worldgen.lithostitched.worldgen.surface.technical.SurfaceRuleManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.DedicatedServer; import org.spongepowered.asm.mixin.Mixin; diff --git a/common/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedMaterialRules.java b/common/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedMaterialRules.java index fa691d9..e1a6f17 100644 --- a/common/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedMaterialRules.java +++ b/common/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedMaterialRules.java @@ -7,6 +7,20 @@ import net.minecraft.world.level.levelgen.SurfaceRules; public final class LithostitchedMaterialRules { + // Technical rules, used for merging modifiers into the main rule public static final ResourceKey> TRANSIENT_MERGED = LithostitchedCommon.createResourceKey(Registries.MATERIAL_RULE, "transient_merged"); - + // Content rules, used for adding new features or simplifying existing constructions + public static final ResourceKey> BILAYER_FILL = LithostitchedCommon.createResourceKey(Registries.MATERIAL_RULE, "bilayer_fill"); + public static final ResourceKey> NOISE_THRESHOLD_SELECTOR = LithostitchedCommon.createResourceKey(Registries.MATERIAL_RULE, "noise_threshold_selector"); + public static final ResourceKey> RANDOM_THRESHOLD_SELECTOR = LithostitchedCommon.createResourceKey(Registries.MATERIAL_RULE, "random_threshold_selector"); + public static final ResourceKey> HEIGHT_THRESHOLD_SELECTOR = LithostitchedCommon.createResourceKey(Registries.MATERIAL_RULE, "height_threshold_selector"); + public static final ResourceKey> STONE_DEPTH_THRESHOLD_SELECTOR = LithostitchedCommon.createResourceKey(Registries.MATERIAL_RULE, "stone_depth_threshold_selector"); + // Material conditions, which are under a separate registry but are used in the TestRule rule so we incl. here + public static final ResourceKey> CLIFF = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "cliff"); + public static final ResourceKey> FLAT = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "flat"); + public static final ResourceKey> FLAT_LIQUID = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "flat_liquid"); + public static final ResourceKey> LAND_TOP_LAYER = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "land_top_layer"); + public static final ResourceKey> UNDERWATER = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "underwater"); + public static final ResourceKey> CAVE_DEPTH = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "cave_depth"); + public static final ResourceKey> EXTENDED_BIOME = LithostitchedCommon.createResourceKey(Registries.MATERIAL_CONDITION, "biome"); } diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/conditions/LithostitchedConditionSources.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/conditions/LithostitchedConditionSources.java new file mode 100644 index 0000000..a03931b --- /dev/null +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/conditions/LithostitchedConditionSources.java @@ -0,0 +1,153 @@ +package dev.worldgen.lithostitched.worldgen.surface.conditions; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.worldgen.lithostitched.worldgen.surface.technical.IContextExtension; +import net.minecraft.core.HolderSet; +import net.minecraft.core.RegistryCodecs; +import net.minecraft.core.registries.Registries; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.SurfaceRules; + +/** + * Surface conditions, used for creating the condition stack. + * @author VoidsongDragonfly + */ +public class LithostitchedConditionSources { + + public enum CliffConditionSource implements SurfaceRules.ConditionSource { + INSTANCE; + + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(MapCodec.unit(INSTANCE)); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + @SuppressWarnings("DataFlowIssue") + public SurfaceRules.Condition apply(SurfaceRules.Context pContext) { + return ((IContextExtension)(Object)pContext).lithostitched$getCliff(); + } + } + + public enum FlatConditionSource implements SurfaceRules.ConditionSource { + INSTANCE; + + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(MapCodec.unit(INSTANCE)); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + @SuppressWarnings("DataFlowIssue") + public SurfaceRules.Condition apply(SurfaceRules.Context pContext) { + return ((IContextExtension)(Object)pContext).lithostitched$getFlat(); + } + } + + public enum FlatLiquidConditionSource implements SurfaceRules.ConditionSource { + INSTANCE; + + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(MapCodec.unit(INSTANCE)); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + @SuppressWarnings("DataFlowIssue") + public SurfaceRules.Condition apply(SurfaceRules.Context pContext) { + return ((IContextExtension)(Object)pContext).lithostitched$getFlatLiquid(); + } + } + + public enum LandTopLayerConditionSource implements SurfaceRules.ConditionSource { + INSTANCE; + + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(MapCodec.unit(INSTANCE)); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + @SuppressWarnings("DataFlowIssue") + public SurfaceRules.Condition apply(SurfaceRules.Context pContext) { + return ((IContextExtension)(Object)pContext).lithostitched$getLandTopLayer(); + } + } + + public record UnderwaterConditionSource(boolean shallow) implements SurfaceRules.ConditionSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of( + RecordCodecBuilder.mapCodec( + source -> source.group( + Codec.BOOL.optionalFieldOf("shallow", false).forGetter(UnderwaterConditionSource::shallow) + ).apply(source, UnderwaterConditionSource::new) + ) + ); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.Condition apply(SurfaceRules.Context pContext) { + return new LithostitchedSurfaceConditions.UnderwaterCondition(pContext, shallow); + } + } + + public record CaveDepthConditionSource(int depth) implements SurfaceRules.ConditionSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of( + RecordCodecBuilder.mapCodec( + source -> source.group( + Codec.INT.fieldOf("depth").forGetter(CaveDepthConditionSource::depth) + ).apply(source, CaveDepthConditionSource::new) + ) + ); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.Condition apply(SurfaceRules.Context pContext) { + return new LithostitchedSurfaceConditions.CaveDepthCondition(pContext, depth); + } + } + + public static class ExtendedBiomeConditionSource implements SurfaceRules.ConditionSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of( + RegistryCodecs.homogeneousList(Registries.BIOME).fieldOf("biome_is").xmap(ExtendedBiomeConditionSource::new, biomeSource -> biomeSource.biomeSet) + ); + public final HolderSet biomeSet; + + public ExtendedBiomeConditionSource(HolderSet biomes) { + this.biomeSet = biomes; + } + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.Condition apply(final SurfaceRules.Context pContext) { + class ExtendedBiomeCondition implements SurfaceRules.Condition { + @Override + public boolean test() { + return biomeSet.contains(pContext.biome.get()); + } + } + + return new ExtendedBiomeCondition(); + } + + @Override + public String toString() { + return "ExtendedBiomeConditionSource[biomes=" + this.biomeSet + "]"; + } + } +} diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/conditions/LithostitchedSurfaceConditions.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/conditions/LithostitchedSurfaceConditions.java new file mode 100644 index 0000000..e04dd47 --- /dev/null +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/conditions/LithostitchedSurfaceConditions.java @@ -0,0 +1,196 @@ +package dev.worldgen.lithostitched.worldgen.surface.conditions; + +import dev.worldgen.lithostitched.worldgen.surface.technical.IContextExtension; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.SurfaceRules; + +/** + * Surface conditions, used for evaluating the condition stack. + * @author VoidsongDragonfly + */ +public class LithostitchedSurfaceConditions { + /** + * Column-lazy condition for checking if this column is a part of a cliff. Avoids cave lips and the edges of cave lips at the bottom side. + * Similar to {@link net.minecraft.world.level.levelgen.SurfaceRules.Context.SteepMaterialCondition `minecraft:steep`} but does not apply only in one cardinal direction and properly determines cliffs from heightmap. + * May break if surface rules are not evaluated from top to bottom; however currently surface rules are evaluated from the top down. + * @author VoidsongDragonfly + */ + public static class CliffCondition extends SurfaceRules.LazyXZCondition { + public CliffCondition(SurfaceRules.Context context) { + super(context); + } + + @Override + protected boolean compute() { + // Variable store for future operations + int i = this.context.blockX & 15; + int j = this.context.blockZ & 15; + ChunkAccess chunk = this.context.chunk; + int north = Math.max(j - 1, 0); + int east = Math.min(i + 1, 15); + int south = Math.min(j + 1, 15); + int west = Math.max(i - 1, 0); + // Heightmap heights + int northHeight = chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, i, north); + int eastHeight = chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, east, j); + int southHeight = chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, i, south); + int westHeight = chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, west, j); + // Get the height difference we need to check to ensure this is a cliff + int difference = (Math.max(northHeight, Math.max(eastHeight, Math.max(southHeight, westHeight))) - Math.min(northHeight, Math.min(eastHeight, Math.min(southHeight, westHeight)))); + // Exit early, to ensure we don't make the more intensive checks. + if (difference < 3) return false; + // Check that we're not on a cliff top-lip. Since the scan is top-down, this catches tops first despite being LazyXZ TODO: possibly not make this apply to big cliff-sides + boolean lip = this.context.stoneDepthBelow <= 2; + // Check that we're not at the bottom of a hanging-over cave entrance. These air checks function because, and so this catches the top despite being LazyXZ + lip = lip || chunk.getBlockState(new BlockPos(this.context.blockX, this.context.blockY+2, this.context.blockZ-j+north)).isAir() && this.context.blockY+2 < northHeight; + lip = lip || chunk.getBlockState(new BlockPos(this.context.blockX-i+east, this.context.blockY+2, this.context.blockZ)).isAir() && this.context.blockY+2 < eastHeight; + lip = lip || chunk.getBlockState(new BlockPos(this.context.blockX, this.context.blockY+2, this.context.blockZ-j+south)).isAir() && this.context.blockY+2 < southHeight; + lip = lip || chunk.getBlockState(new BlockPos(this.context.blockX-i+west, this.context.blockY+2, this.context.blockZ)).isAir() && this.context.blockY+2 < westHeight; + return !lip; + } + } + + /** + * Column-lazy condition for checking if this column is perfectly flat on the surface. + * Does not work 100% faithfully on chunk borders due to the vagarities of surface rule chunk access, may return true when not totally flat. + * @author VoidsongDragonfly + */ + public static class FlatCondition extends SurfaceRules.LazyXZCondition { + public FlatCondition(SurfaceRules.Context context) { + super(context); + } + + @Override + protected boolean compute() { + // Shift to chunkwise coordinates + int i = this.context.blockX & 15; + int j = this.context.blockZ & 15; + // Ensure we're not outside the chunk + int north = Math.max(j - 1, 0); + int east = Math.min(i + 1, 15); + int south = Math.min(j + 1, 15); + int west = Math.max(i - 1, 0); + // Check the heightmaps of the neighboring blocks at water-level. This is not optimized because it's only used in one rule currently. + ChunkAccess chunk = this.context.chunk; + int northHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, i, north); + int eastHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, east, j); + int southHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, i, south); + int westHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, west, j); + // Check deviation from expected height + return Math.max(northHeight, Math.max(southHeight, Math.max(westHeight, eastHeight))) - Math.min(northHeight, Math.min(southHeight, Math.min(westHeight, eastHeight))) == 0 && northHeight == chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, i, j); + } + } + + /** + * Column-lazy condition for checking if this column is perfectly flat on the surface. + * Will return true only when verifiably flat; this by necessity cuts out chunk borders above sea level despite their possibility of being flat. + * Intended for use with liquids such as water which MUST be flat or else they will flow. + * @author VoidsongDragonfly + */ + public static class FlatLiquidCondition extends SurfaceRules.LazyXZCondition { + public FlatLiquidCondition(SurfaceRules.Context context) { + super(context); + } + + @Override + protected boolean compute() { + // Shift to chunkwise coordinates + int i = this.context.blockX & 15; + int j = this.context.blockZ & 15; + // Ensure we're not outside the chunk + int north = Math.max(j - 1, 0); + int east = Math.min(i + 1, 15); + int south = Math.min(j + 1, 15); + int west = Math.max(i - 1, 0); + // Check the heightmaps of the neighboring blocks at water-level. This is not optimized because it's only used in one rule currently. + ChunkAccess chunk = this.context.chunk; + int northHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, i, north); + int eastHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, east, j); + int southHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, i, south); + int westHeight = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, west, j); + // Since we scan from the top down, we can ensure we're flat and not going to spill water downwards + boolean bottom = !chunk.getBlockState(new BlockPos(this.context.blockX, this.context.blockY - 1, this.context.blockZ)).canBeReplaced(); + // Check deviation from expected height + boolean flat = Math.max(northHeight, Math.max(southHeight, Math.max(westHeight, eastHeight))) - Math.min(northHeight, Math.min(southHeight, Math.min(westHeight, eastHeight))) == 0 && northHeight == chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, i, j); + // The check to return false on chunk borders is a massive kludge, but I use this with _water_. I can't afford flowing water.... + boolean nonChunkBorder = !(((north == j || south == j)||(west == i || east == i)) && this.context.blockY > 63); + // Combine all the checks together + return flat && bottom && nonChunkBorder; + } + } + + /** + * Non-lazy condition intended for simplifying non-water-top-layer-of-surface checks + * @param context the {@link SurfaceRules.Context context} the condition evaluates with + * @author VoidsongDragonfly + */ + public record LandTopLayerCondition(SurfaceRules.Context context) implements SurfaceRules.Condition { + @Override + public boolean test() { + return context.waterHeight == Integer.MIN_VALUE && context.stoneDepthAbove <= 1; + } + } + + /** + * Non-lazy condition intended to simplify the process of checking whether a block is underwater + * Shallowness follows the Vanilla {@link net.minecraft.world.level.levelgen.SurfaceRules.WaterConditionSource `minecraft:water`} condition as used in Vanilla surface rules. + * @param context the {@link SurfaceRules.Context context} the condition evaluates with + * @param shallow boolean for whether to check for shallow water only or to pass any underwater block + * @author VoidsongDragonfly + */ + public record UnderwaterCondition(SurfaceRules.Context context, boolean shallow) implements SurfaceRules.Condition { + @Override + public boolean test() { + // Exit early if we're above water + if (context.waterHeight == Integer.MIN_VALUE) return false; + // If we don't care about shallowness, return early, else check the Vanilla "shallow water" parameters + return !shallow || ((context.blockY + context.stoneDepthAbove) >= (context.waterHeight - 6 - context.surfaceDepth)); + } + } + + /** + * Non-lazy condition intended for use in ensuring grass and other surface blocks are not placed in subsurface caves. + * Is not a perfect condition, is mostly intended as a good-enough heuristic to ensure the above. WILL pass true on some above-ground overhangs. + * Intended to pass false on most overhangs that would let light into the blocks below them, but may not succeed. + * Checks if a position is more than a set depth below the world surface and returns true if so. + * @param context the {@link SurfaceRules.Context context} the condition evaluates with + * @param depth the depth below the {@link Heightmap.Types `OCEAN_FLOOR_WG`} heightmap that this block must be lower than to return true + * @author VoidsongDragonfly + */ + public record CaveDepthCondition(SurfaceRules.Context context, int depth) implements SurfaceRules.Condition { + @Override + public boolean test() { + int heightmapDepth = ((IContextExtension)(Object)context).lithostitched$getOceanHeightmapDepth(); + // Return early if this isn't a cave - ie, if the ground above is solid + if (context.stoneDepthAbove >= (heightmapDepth-context.blockY+1)) return false; + // Return early if we're above the necessary depth + if (heightmapDepth - depth <= context.blockY) return false; + // If we're shallower than twelve blocks, we do not need to check the air blocks above this block + // We remove/add stoneDepthAbove to make sure we stay congruous with the top block of the cave + int currentDepth = heightmapDepth - context.blockY + context.stoneDepthAbove; + if (currentDepth < 12) return true; + // Check to make sure we're not underneath a massive overhang by checking if greater than 2/3ths what's above is air + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(context.blockX, context.blockY + context.stoneDepthAbove, context.blockZ); + for (int i = 1 + context.stoneDepthAbove; i < (currentDepth*3)/4; i++) + if (!context.chunk.getBlockState(pos.setY(context.blockY + i)).canBeReplaced()) return true; + // Variable store for future operations + int i = context.blockX & 15; + int j = context.blockZ & 15; + // Movements within the chunk for close block checks + int searchLevel = context.blockY + context.stoneDepthAbove- 2; + int north = Math.max(j - 1, 0); + int east = Math.min(i + 1, 15); + int south = Math.min(j + 1, 15); + int west = Math.max(i - 1, 0); + // Now we check to make sure we're not on the side of a cliff in a windswept biome + boolean lip = false; + lip = lip || context.chunk.getBlockState(new BlockPos(context.blockX, searchLevel, context.blockZ-j+north)).isAir(); + lip = lip || context.chunk.getBlockState(new BlockPos(context.blockX-i+east, searchLevel, context.blockZ)).isAir(); + lip = lip || context.chunk.getBlockState(new BlockPos(context.blockX, searchLevel, context.blockZ-j+south)).isAir(); + lip = lip || context.chunk.getBlockState(new BlockPos(context.blockX-i+west, searchLevel, context.blockZ)).isAir(); + return lip; + } + } +} diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/rules/LithostitchedRuleSources.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/rules/LithostitchedRuleSources.java new file mode 100644 index 0000000..ef7f89c --- /dev/null +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/rules/LithostitchedRuleSources.java @@ -0,0 +1,165 @@ +package dev.worldgen.lithostitched.worldgen.surface.rules; + +import com.google.common.collect.ImmutableList; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.synth.NormalNoise; +import dev.worldgen.lithostitched.worldgen.surface.rules.LithostitchedSurfaceRules.*; + +import java.util.List; +import java.util.Optional; + +/** + * Surface rule sources, used for creating the surface rule stack. + * @author VoidsongDragonfly + */ +public class LithostitchedRuleSources { + public record NoiseThresholdSelectorRuleSource(ResourceKey noise, SurfaceRules.RuleSource defaultRule, List ruleset, List lowerThresholds, boolean cascade) implements SurfaceRules.RuleSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec( + instance -> instance.group( + ResourceKey.codec(Registries.NOISE).fieldOf("noise").forGetter(NoiseThresholdSelectorRuleSource::noise), + SurfaceRules.RuleSource.CODEC.optionalFieldOf("default_rule", new PassthroughRuleSource()).forGetter(NoiseThresholdSelectorRuleSource::defaultRule), + SurfaceRules.RuleSource.CODEC.listOf().fieldOf("ruleset").forGetter(NoiseThresholdSelectorRuleSource::ruleset), + Codec.DOUBLE.listOf().fieldOf("lower_noise_thresholds").forGetter(NoiseThresholdSelectorRuleSource::lowerThresholds), + Codec.BOOL.optionalFieldOf("cascade", false).forGetter(NoiseThresholdSelectorRuleSource::cascade) + ).apply(instance, NoiseThresholdSelectorRuleSource::new) + )); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context pContext) { + // Check if we have a singleton list for lower thresholds && ruleset + if (ruleset.size() == 1 && lowerThresholds.size() == 1) + return new NoiseThresholdRule(pContext, noise, defaultRule.apply(pContext), ruleset.getFirst().apply(pContext), lowerThresholds.getFirst()); + // Follow what SurfaceRules$SequenceRuleSource#apply() does and use an immutable list builder + ImmutableList.Builder builder = ImmutableList.builder(); + for (SurfaceRules.RuleSource ruleSource : this.ruleset) + builder.add(ruleSource.apply(pContext)); + // Return a new rule with the necessary parameters + return new NoiseThresholdSelectorRule(pContext, noise, defaultRule.apply(pContext), builder.build(), lowerThresholds, cascade); + } + } + + public record RandomThresholdSelectorRuleSource(ResourceLocation randomName, Optional defaultState, List stateSet, List lowerThresholds) implements SurfaceRules.RuleSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec( + instance -> instance.group( + ResourceLocation.CODEC.fieldOf("random_name").forGetter(RandomThresholdSelectorRuleSource::randomName), + BlockState.CODEC.optionalFieldOf("default_state").forGetter(RandomThresholdSelectorRuleSource::defaultState), + BlockState.CODEC.listOf().fieldOf("state_set").forGetter(RandomThresholdSelectorRuleSource::stateSet), + Codec.DOUBLE.listOf().fieldOf("lower_random_thresholds").forGetter(RandomThresholdSelectorRuleSource::lowerThresholds) + ).apply(instance, RandomThresholdSelectorRuleSource::new) + )); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context pContext) { + // The random factory can be created outside the rule itself (see VerticalGradientRuleSource) + final PositionalRandomFactory positionalRandomFactory = pContext.randomState.getOrCreateRandomFactory(this.randomName()); + // Check if we have a singleton list for lower thresholds && ruleset + if (stateSet.size() == 1 && lowerThresholds.size() == 1) + return new RandomThresholdRule(pContext, positionalRandomFactory, defaultState.orElse(null), stateSet.getFirst(), lowerThresholds.getFirst()); + // Return a new rule with the necessary parameters + return new RandomThresholdSelectorRule(pContext, positionalRandomFactory, defaultState.orElse(null), stateSet, lowerThresholds); + } + } + + public record HeightThresholdSelectorRuleSource(SurfaceRules.RuleSource defaultRule, List ruleset, List lowerThresholds, int surfaceDepthMultiplier, boolean addStoneDepth, boolean cascade) implements SurfaceRules.RuleSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec( + instance -> instance.group( + SurfaceRules.RuleSource.CODEC.optionalFieldOf("default_rule", new PassthroughRuleSource()).forGetter(HeightThresholdSelectorRuleSource::defaultRule), + SurfaceRules.RuleSource.CODEC.listOf().fieldOf("ruleset").forGetter(HeightThresholdSelectorRuleSource::ruleset), + Codec.INT.listOf().fieldOf("lower_height_thresholds").forGetter(HeightThresholdSelectorRuleSource::lowerThresholds), + Codec.intRange(-20, 20).optionalFieldOf("surface_depth_multiplier", 0).forGetter(HeightThresholdSelectorRuleSource::surfaceDepthMultiplier), + Codec.BOOL.optionalFieldOf("add_stone_depth", false).forGetter(HeightThresholdSelectorRuleSource::addStoneDepth), + Codec.BOOL.optionalFieldOf("cascade", false).forGetter(HeightThresholdSelectorRuleSource::cascade) + ).apply(instance, HeightThresholdSelectorRuleSource::new) + )); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context pContext) { + // Check if we have a singleton list for lower thresholds && ruleset + if (ruleset.size() == 1 && lowerThresholds.size() == 1) + return new HeightThresholdRule(pContext, defaultRule.apply(pContext), ruleset.getFirst().apply(pContext), lowerThresholds.getFirst(), surfaceDepthMultiplier, addStoneDepth); + // Follow what SurfaceRules$SequenceRuleSource#apply() does and use an immutable list builder + ImmutableList.Builder builder = ImmutableList.builder(); + for (SurfaceRules.RuleSource ruleSource : this.ruleset) + builder.add(ruleSource.apply(pContext)); + // Return a new rule with the necessary parameters + return new HeightThresholdSelectorRule(pContext, defaultRule.apply(pContext), builder.build(), lowerThresholds, surfaceDepthMultiplier, addStoneDepth, cascade); + } + } + + public record StoneDepthThresholdSelectorRuleSource(SurfaceRules.RuleSource defaultRule, List ruleset) implements SurfaceRules.RuleSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec( + instance -> instance.group( + SurfaceRules.RuleSource.CODEC.optionalFieldOf("default_rule", new PassthroughRuleSource()).forGetter(StoneDepthThresholdSelectorRuleSource::defaultRule), + SurfaceRules.RuleSource.CODEC.listOf().fieldOf("ruleset").forGetter(StoneDepthThresholdSelectorRuleSource::ruleset) + ).apply(instance, StoneDepthThresholdSelectorRuleSource::new) + )); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context pContext) { + // Follow what SurfaceRules$SequenceRuleSource#apply() does and use an immutable list builder + ImmutableList.Builder builder = ImmutableList.builder(); + for (SurfaceRules.RuleSource ruleSource : this.ruleset) + builder.add(ruleSource.apply(pContext)); + // Return a new rule with the necessary parameters + return new StoneDepthThresholdSelectorRule(pContext, defaultRule.apply(pContext), builder.build(), ruleset.size()); + } + } + + public record BilayerFillRuleSource(boolean land, int surfaceOffset, int secondaryDepthRange, SurfaceRules.RuleSource topRule, SurfaceRules.RuleSource defaultRule) implements SurfaceRules.RuleSource { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec( + instance -> instance.group( + Codec.BOOL.optionalFieldOf("land", true).forGetter(BilayerFillRuleSource::land), + Codec.INT.optionalFieldOf("surface_offset", 0).forGetter(BilayerFillRuleSource::surfaceOffset), + Codec.INT.optionalFieldOf("secondary_depth_range", 0).forGetter(BilayerFillRuleSource::secondaryDepthRange), + SurfaceRules.RuleSource.CODEC.fieldOf("top_layer").forGetter(BilayerFillRuleSource::topRule), + SurfaceRules.RuleSource.CODEC.fieldOf("sublayer").forGetter(BilayerFillRuleSource::defaultRule) + ).apply(instance, BilayerFillRuleSource::new) + )); + + @Override + public KeyDispatchDataCodec codec() { + return CODEC; + } + + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context pContext) { + // Return a new rule with the necessary parameters + return new BilayerFillRule(pContext, land, surfaceOffset, secondaryDepthRange, topRule.apply(pContext), defaultRule.apply(pContext)); + } + } + + private static class PassthroughRuleSource implements SurfaceRules.RuleSource { + @Override + @SuppressWarnings("all") + public KeyDispatchDataCodec codec() { + return null; + } + + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context pContext) { + return (x, y, z) -> null; + } + } +} diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/rules/LithostitchedSurfaceRules.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/rules/LithostitchedSurfaceRules.java new file mode 100644 index 0000000..1c841fa --- /dev/null +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/rules/LithostitchedSurfaceRules.java @@ -0,0 +1,206 @@ +package dev.worldgen.lithostitched.worldgen.surface.rules; + +import com.google.common.collect.ImmutableList; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +import java.util.List; + +/** + * Surface rules, used for evaluating the surface rule stack. + * @author VoidsongDragonfly + */ +public class LithostitchedSurfaceRules { + /** + * Noise selector surface rule; iterates down a list of noise thresholds to evaluate and return the rule within those thresholds + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param noise the {@link NormalNoise noise} to evaluate for the thresholds + * @param defaultRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate if all other rules fail + * @param ruleset the list containing surface rules to evaluate + * @param lowerThresholds the list containing thresholds to evaluate for + * @param cascade boolean for whether we should cascade down surface rules if one fails to resolve in this noise bin + * @author VoidsongDragonfly + */ + record NoiseThresholdSelectorRule(SurfaceRules.Context pContext, ResourceKey noise, SurfaceRules.SurfaceRule defaultRule, ImmutableList ruleset, List lowerThresholds, boolean cascade) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Check the noise we're using for values, and grab our double value + double d0 = pContext.randomState.getOrCreateNoise(noise).getValue(x, 0.0, z); + // Iterate through the rules to figure out which rule to provide, and return the rule for the noise bin we're in + BlockState result = null; + for(int i = 0; i < Math.min(lowerThresholds.size(), ruleset().size()); i++) { + if(d0 > lowerThresholds.get(i)) { + result = ruleset.get(i).tryApply(x, y, z); + // Break the loop if we have gotten a value OR we're not cascading + if (result != null || !cascade) break; + } + } + // Return the default rule if we're not in any noise bin or have a noise bin that does not resolve + return result == null ? defaultRule.tryApply(x, y, z) : result; + } + } + + /** + * Noise selector surface rule; used when we have only one {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} and a default rule to optimize generation speed & allocation + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param noise the {@link NormalNoise noise} to evaluate for the threshold + * @param defaultRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate if the other rule fails + * @param rule the primary {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate + * @param lowerThreshold the double threshold to evaluate + * @author VoidsongDragonfly + */ + record NoiseThresholdRule(SurfaceRules.Context pContext, ResourceKey noise, SurfaceRules.SurfaceRule defaultRule, SurfaceRules.SurfaceRule rule, Double lowerThreshold) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Check the noise we're using for values, and grab our double value + double d0 = pContext.randomState.getOrCreateNoise(noise).getValue(x, 0.0, z); + // Apply the rule and store the result, with null if we are not within the noise bin + BlockState result = d0 > lowerThreshold ? rule.tryApply(x, y, z) : null; + // Return the default rule if we're not in the noise bin or have a noise bin that does not resolve + return result == null ? defaultRule.tryApply(x, y, z) : null; + } + } + + /** + * Random selector surface rule; iterates down a list of random thresholds to evaluate and return the state within those thresholds + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param positionalRandomFactory the {@link PositionalRandomFactory factory } to evaluate for random values + * @param defaultState the {@link BlockState state} to return if all other rules fail + * @param stateSet the list containing block states to evaluate + * @param lowerThresholds the list containing thresholds to evaluate for + * @author VoidsongDragonfly + */ + record RandomThresholdSelectorRule(SurfaceRules.Context pContext, PositionalRandomFactory positionalRandomFactory, BlockState defaultState, List stateSet, List lowerThresholds) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Check the random we're using for values, and grab our double value + RandomSource randomSource = positionalRandomFactory.at(pContext.blockX, pContext.blockY, pContext.blockZ); + double d0 = randomSource.nextDouble(); + // Iterate through the rules to figure out which rule to provide, and return the rule for the noise bin we're in + for(int i = 0; i < Math.min(lowerThresholds.size(), stateSet().size()); i++) { + if(d0 > lowerThresholds.get(i)) return stateSet.get(i); + } + // Return the default rule if we're not in any random bin + return defaultState; + } + } + + /** + * Random selector surface rule; used when we have only one {@link BlockState state} and a default state to optimize generation speed & allocation + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param positionalRandomFactory the {@link PositionalRandomFactory factory } to evaluate for random values + * @param defaultState the {@link BlockState state} to return if the other state fails + * @param state the {@link BlockState state} to return + * @author VoidsongDragonfly + */ + record RandomThresholdRule(SurfaceRules.Context pContext, PositionalRandomFactory positionalRandomFactory, BlockState defaultState, BlockState state, Double lowerThreshold) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Check the random we're using for values, and grab our double value + RandomSource randomSource = positionalRandomFactory.at(pContext.blockX, pContext.blockY, pContext.blockZ); + // Return the state if we're in the random bin, because blockstates to place can't be null + if(randomSource.nextDouble() > lowerThreshold) return state; + // Return the default rule if we're not in the random bin + return defaultState; + } + } + + /** + * Height selector surface rule; iterates down a list of height thresholds to evaluate and return the rule within those thresholds + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param defaultRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate if all other rules fail + * @param ruleset the list containing surface rules to evaluate + * @param lowerThresholds the list containing thresholds to evaluate for + * @param cascade boolean for whether we should cascade down surface rules if one fails to resolve in this noise bin + * @author VoidsongDragonfly + */ + record HeightThresholdSelectorRule(SurfaceRules.Context pContext, SurfaceRules.SurfaceRule defaultRule, ImmutableList ruleset, List lowerThresholds, int surfaceDepthMultiplier, boolean addStoneDepth, boolean cascade) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Get the height that we want to compare against + int comparisonYValue = pContext.blockY + (addStoneDepth ? pContext.stoneDepthAbove : 0) - pContext.surfaceDepth * surfaceDepthMultiplier; + // Iterate through the rules to figure out which rule to provide, and return the rule for the height bin we're in + BlockState result = null; + for(int i = 0; i < Math.min(lowerThresholds.size(), ruleset().size()); i++) { + if(comparisonYValue > lowerThresholds.get(i)) { + result = ruleset.get(i).tryApply(x, y, z); + // Break the loop if we have gotten a value OR we're not cascading + if (result != null || !cascade) break; + } + } + // Return the default rule if we're not in any height bin or have a height bin that does not resolve + return result == null ? defaultRule.tryApply(x, y, z) : result; + } + } + + /** + * Height selector surface rule; used when we have only one {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} and a default rule to optimize generation speed & allocation + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param defaultRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate if the other rule fails + * @param rule the primary {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate + * @param lowerThreshold the integer threshold to evaluate + * @author VoidsongDragonfly + */ + record HeightThresholdRule(SurfaceRules.Context pContext, SurfaceRules.SurfaceRule defaultRule, SurfaceRules.SurfaceRule rule, int lowerThreshold, int surfaceDepthMultiplier, boolean addStoneDepth) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Get the height that we want to compare against + int comparisonYValue = pContext.blockY + (addStoneDepth ? pContext.stoneDepthAbove : 0) - pContext.surfaceDepth * surfaceDepthMultiplier; + // Apply the rule and store the result, with null if we are not within the height bin + BlockState result = comparisonYValue > lowerThreshold ? rule.tryApply(x, y, z) : null; + // Return the default rule if we're not in the height bin or have a height bin that does not resolve + return result == null ? defaultRule.tryApply(x, y, z) : null; + } + } + + /** + * Depth below the surface rule selector; picks from the ruleset the surface rule which matches the number of blocks above said block + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param defaultRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate if all other rules fail + * @param ruleset the list containing surface rules to select from by stone depth + * @param length the length of the list we are evaluating from, for selection bounds limiting + * @author VoidsongDragonfly + */ + record StoneDepthThresholdSelectorRule(SurfaceRules.Context pContext, SurfaceRules.SurfaceRule defaultRule, ImmutableList ruleset, int length) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Get the rule we want at the specified depth and evaluate it for this position + BlockState result = pContext.stoneDepthAbove >= length ? null : ruleset.get(pContext.stoneDepthAbove - 1).tryApply(x, y, z); + // Return the default rule if we're not in any depth bin or have a height bin that does not resolve + return result == null ? defaultRule.tryApply(x, y, z) : result; + } + } + + /** + * Dual-layer fill surface rule intended for dirt-and-grass fills. Top layer is separated from the bottom layers, which are all the same fill. + * @param pContext the {@link SurfaceRules.Context context} the rule generates with + * @param land boolean for if we should only be evaluating if this is on land or underwater + * @param surfaceOffset the integer number of extra blocks to add to the bottom of the sublayer + * @param secondaryDepthRange the integer range to which secondary depth noise should be clamped then added to the bottom of the sublayer + * @param topRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate for the top layer + * @param sublayerRule the {@link net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule rule} to evaluate for the bottom layer + * @author VoidsongDragonfly + */ + record BilayerFillRule(SurfaceRules.Context pContext, boolean land, int surfaceOffset, int secondaryDepthRange, SurfaceRules.SurfaceRule topRule, SurfaceRules.SurfaceRule sublayerRule) implements SurfaceRules.SurfaceRule { + @Override + public BlockState tryApply(int x, int y, int z) { + // Check to make sure we are in the correct land/water bin and return null if we fail + if(land == (pContext.waterHeight != Integer.MIN_VALUE)) return null; + // Check which bin we're in for surface rules + if(pContext.stoneDepthAbove <= 1) + return topRule.tryApply(x, y, z); + // Calculate the secondary depth we need to check against, zero for no depth; this is after top check for performance + int secondary = secondaryDepthRange == 0 ? 0 : (int) Mth.map(pContext.getSurfaceSecondary(), -1.0, 1.0, 0.0, secondaryDepthRange); + // Second bin necessitates more checks to form the 'bottom' effectively + if(pContext.stoneDepthAbove <= 1 + surfaceOffset + pContext.surfaceDepth + secondary) + return sublayerRule.tryApply(x, y, z); + // Return a null BlockState in if we fail to be in either bin + else return null; + } + } +} diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/IContextExtension.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/IContextExtension.java new file mode 100644 index 0000000..faf3ffb --- /dev/null +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/IContextExtension.java @@ -0,0 +1,13 @@ +package dev.worldgen.lithostitched.worldgen.surface.technical; + +import net.minecraft.world.level.levelgen.SurfaceRules; + +public interface IContextExtension { + // Rule return functions for cached LazyXZCondition rules + SurfaceRules.Condition lithostitched$getCliff(); + SurfaceRules.Condition lithostitched$getFlat(); + SurfaceRules.Condition lithostitched$getFlatLiquid(); + SurfaceRules.Condition lithostitched$getLandTopLayer(); + // Value return functions for cached parameterized rules & conditions + int lithostitched$getOceanHeightmapDepth(); +} diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/LithostitchedSurfaceRules.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/LithostitchedTechnicalRules.java similarity index 68% rename from common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/LithostitchedSurfaceRules.java rename to common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/LithostitchedTechnicalRules.java index 7c66ea0..9dc0f80 100644 --- a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/LithostitchedSurfaceRules.java +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/LithostitchedTechnicalRules.java @@ -1,26 +1,12 @@ -package dev.worldgen.lithostitched.worldgen.surface; +package dev.worldgen.lithostitched.worldgen.surface.technical; import com.google.common.collect.ImmutableList; import net.minecraft.util.KeyDispatchDataCodec; -import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.SurfaceRules; import java.util.List; -public class LithostitchedSurfaceRules extends SurfaceRules { - private record SequenceRule(List rules) implements SurfaceRules.SurfaceRule { - @Override - public BlockState tryApply(int x, int y, int z) { - for (SurfaceRules.SurfaceRule surfaceRule : this.rules) { - BlockState blockstate = surfaceRule.tryApply(x, y, z); - if (blockstate != null) { - return blockstate; - } - } - return null; - } - } - +public class LithostitchedTechnicalRules extends SurfaceRules { /** * The {@link RuleSource} type responsible for merging new surface rules with original surface rules. * @@ -42,7 +28,7 @@ public KeyDispatchDataCodec codec() { @Override public SurfaceRules.SurfaceRule apply(SurfaceRules.Context context) { if (this.sequence.size() == 1) { - return this.sequence.get(0).apply(context); + return this.sequence.getFirst().apply(context); } else { ImmutableList.Builder builder = ImmutableList.builder(); for (SurfaceRules.RuleSource ruleSource : this.sequence) { diff --git a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/SurfaceRuleManager.java b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/SurfaceRuleManager.java similarity index 91% rename from common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/SurfaceRuleManager.java rename to common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/SurfaceRuleManager.java index 1b504bc..a931efd 100644 --- a/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/SurfaceRuleManager.java +++ b/common/src/main/java/dev/worldgen/lithostitched/worldgen/surface/technical/SurfaceRuleManager.java @@ -1,4 +1,4 @@ -package dev.worldgen.lithostitched.worldgen.surface; +package dev.worldgen.lithostitched.worldgen.surface.technical; import com.mojang.datafixers.util.Pair; import dev.worldgen.lithostitched.LithostitchedCommon; @@ -81,11 +81,11 @@ private static SurfaceRules.RuleSource buildModdedSurfaceRules(ArrayList newRuleSourceList.add(pair.getSecond().surfaceRule())); newRuleSourceList.add(originalSource); - if (originalSource instanceof LithostitchedSurfaceRules.TransientMergedRuleSource) { - ((LithostitchedSurfaceRules.TransientMergedRuleSource) originalSource).sequence().addAll(newRuleSourceList); + if (originalSource instanceof LithostitchedTechnicalRules.TransientMergedRuleSource) { + ((LithostitchedTechnicalRules.TransientMergedRuleSource) originalSource).sequence().addAll(newRuleSourceList); return originalSource; } else { - return new LithostitchedSurfaceRules.TransientMergedRuleSource(newRuleSourceList, originalSource); + return new LithostitchedTechnicalRules.TransientMergedRuleSource(newRuleSourceList, originalSource); } } } diff --git a/common/src/main/resources/lithostitched.accesswidener b/common/src/main/resources/lithostitched.accesswidener index 1f6b09b..8b6b9ca 100644 --- a/common/src/main/resources/lithostitched.accesswidener +++ b/common/src/main/resources/lithostitched.accesswidener @@ -7,4 +7,30 @@ accessible class net/minecraft/world/level/levelgen/DensityFunctions$EndIslandDe accessible class net/minecraft/world/level/biome/Biome$ClimateSettings -accessible method net/minecraft/world/level/levelgen/feature/stateproviders/BlockStateProviderType (Lcom/mojang/serialization/MapCodec;)V \ No newline at end of file +accessible method net/minecraft/world/level/levelgen/feature/stateproviders/BlockStateProviderType (Lcom/mojang/serialization/MapCodec;)V + + +# Non-context access necessary for new surface rules +accessible class net/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule +accessible class net/minecraft/world/level/levelgen/SurfaceRules$Condition +accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyXZCondition +accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyYCondition +accessible class net/minecraft/world/level/levelgen/SurfaceRules$BiomeConditionSource +accessible class net/minecraft/world/level/levelgen/SurfaceRules$WaterConditionSource +accessible class net/minecraft/world/level/levelgen/SurfaceRules$StoneDepthCheck +accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRule +accessible method net/minecraft/world/level/levelgen/SurfaceRules$SequenceRule (Ljava/util/List;)V +# Access to allow working with Context +accessible class net/minecraft/world/level/levelgen/SurfaceRules$Context +accessible class net/minecraft/world/level/levelgen/SurfaceRules$Context$SteepMaterialCondition +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context blockX I +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context blockY I +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context blockZ I +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context biome Ljava/util/function/Supplier; +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context chunk Lnet/minecraft/world/level/chunk/ChunkAccess; +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context stoneDepthBelow I +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context randomState Lnet/minecraft/world/level/levelgen/RandomState; +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context waterHeight I +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context stoneDepthAbove I +accessible field net/minecraft/world/level/levelgen/SurfaceRules$Context surfaceDepth I +accessible method net/minecraft/world/level/levelgen/SurfaceRules$Context getSurfaceSecondary ()D \ No newline at end of file diff --git a/common/src/main/resources/lithostitched.mixins.json b/common/src/main/resources/lithostitched.mixins.json index 5ea9d69..a740e0a 100644 --- a/common/src/main/resources/lithostitched.mixins.json +++ b/common/src/main/resources/lithostitched.mixins.json @@ -7,6 +7,7 @@ "mixins": [ "common.ChunkGeneratorAccessor", "common.ChunkMapMixin", + "common.ContextMixin", "common.GameTestServerMixin", "common.HolderReferenceAccessor", "common.JigsawStructureAccessor", diff --git a/fabric/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java b/fabric/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java index 5fc0bcc..a5fdf9a 100644 --- a/fabric/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java +++ b/fabric/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java @@ -6,7 +6,9 @@ import dev.worldgen.lithostitched.worldgen.modifier.*; import dev.worldgen.lithostitched.worldgen.processor.condition.ProcessorCondition; import dev.worldgen.lithostitched.worldgen.placementcondition.PlacementCondition; -import dev.worldgen.lithostitched.worldgen.surface.LithostitchedSurfaceRules; +import dev.worldgen.lithostitched.worldgen.surface.conditions.LithostitchedConditionSources; +import dev.worldgen.lithostitched.worldgen.surface.rules.LithostitchedRuleSources; +import dev.worldgen.lithostitched.worldgen.surface.technical.LithostitchedTechnicalRules; import net.fabricmc.fabric.api.event.registry.DynamicRegistries; import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions; @@ -17,7 +19,7 @@ import java.util.function.BiConsumer; import static dev.worldgen.lithostitched.LithostitchedCommon.createResourceKey; -import static dev.worldgen.lithostitched.registry.LithostitchedMaterialRules.TRANSIENT_MERGED; +import static dev.worldgen.lithostitched.registry.LithostitchedMaterialRules.*; /** * Built-in registries for Lithostitched on Fabric. @@ -48,7 +50,21 @@ public static void init() { LithostitchedCommon.registerCommonStructureProcessors((name, type) -> register(BuiltInRegistries.STRUCTURE_PROCESSOR, name, type)); LithostitchedCommon.registerCommonBlockEntityModifiers((name, type) -> register(BuiltInRegistries.RULE_BLOCK_ENTITY_MODIFIER, name, type)); - Registry.register(BuiltInRegistries.MATERIAL_RULE, TRANSIENT_MERGED, LithostitchedSurfaceRules.TransientMergedRuleSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_RULE, TRANSIENT_MERGED, LithostitchedTechnicalRules.TransientMergedRuleSource.CODEC.codec()); + + Registry.register(BuiltInRegistries.MATERIAL_RULE, BILAYER_FILL, LithostitchedRuleSources.BilayerFillRuleSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_RULE, NOISE_THRESHOLD_SELECTOR, LithostitchedRuleSources.NoiseThresholdSelectorRuleSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_RULE, RANDOM_THRESHOLD_SELECTOR, LithostitchedRuleSources.RandomThresholdSelectorRuleSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_RULE, HEIGHT_THRESHOLD_SELECTOR, LithostitchedRuleSources.HeightThresholdSelectorRuleSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_RULE, STONE_DEPTH_THRESHOLD_SELECTOR, LithostitchedRuleSources.StoneDepthThresholdSelectorRuleSource.CODEC.codec()); + + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, CLIFF, LithostitchedConditionSources.CliffConditionSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, FLAT, LithostitchedConditionSources.FlatConditionSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, FLAT_LIQUID, LithostitchedConditionSources.FlatLiquidConditionSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, LAND_TOP_LAYER, LithostitchedConditionSources.LandTopLayerConditionSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, UNDERWATER, LithostitchedConditionSources.UnderwaterConditionSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, CAVE_DEPTH, LithostitchedConditionSources.CaveDepthConditionSource.CODEC.codec()); + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, EXTENDED_BIOME, LithostitchedConditionSources.ExtendedBiomeConditionSource.CODEC.codec()); ResourceConditions.register(BreaksSeedParityCondition.TYPE); } diff --git a/neoforge/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java b/neoforge/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java index cd2107f..6e03a0a 100644 --- a/neoforge/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java +++ b/neoforge/src/main/java/dev/worldgen/lithostitched/registry/LithostitchedBuiltInRegistries.java @@ -6,7 +6,9 @@ import dev.worldgen.lithostitched.worldgen.modifier.*; import dev.worldgen.lithostitched.worldgen.processor.condition.ProcessorCondition; import dev.worldgen.lithostitched.worldgen.placementcondition.PlacementCondition; -import dev.worldgen.lithostitched.worldgen.surface.LithostitchedSurfaceRules; +import dev.worldgen.lithostitched.worldgen.surface.conditions.LithostitchedConditionSources; +import dev.worldgen.lithostitched.worldgen.surface.rules.LithostitchedRuleSources; +import dev.worldgen.lithostitched.worldgen.surface.technical.LithostitchedTechnicalRules; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; @@ -22,6 +24,7 @@ import static dev.worldgen.lithostitched.LithostitchedCommon.MOD_ID; import static dev.worldgen.lithostitched.LithostitchedCommon.createResourceKey; +import static dev.worldgen.lithostitched.registry.LithostitchedMaterialRules.*; /** * Built-in registries for Lithostitched on Neoforge. @@ -41,7 +44,21 @@ public final class LithostitchedBuiltInRegistries { public static void init(IEventBus bus) { bus.addListener((RegisterEvent event) -> { - event.register(Registries.MATERIAL_RULE, helper -> helper.register(createResourceKey(Registries.MATERIAL_RULE, "transient_merged"), LithostitchedSurfaceRules.TransientMergedRuleSource.CODEC.codec())); + event.register(Registries.MATERIAL_RULE, helper -> helper.register(TRANSIENT_MERGED, LithostitchedTechnicalRules.TransientMergedRuleSource.CODEC.codec())); + + event.register(Registries.MATERIAL_RULE, helper -> helper.register(BILAYER_FILL, LithostitchedRuleSources.BilayerFillRuleSource.CODEC.codec())); + event.register(Registries.MATERIAL_RULE, helper -> helper.register(NOISE_THRESHOLD_SELECTOR, LithostitchedRuleSources.NoiseThresholdSelectorRuleSource.CODEC.codec())); + event.register(Registries.MATERIAL_RULE, helper -> helper.register(RANDOM_THRESHOLD_SELECTOR, LithostitchedRuleSources.RandomThresholdSelectorRuleSource.CODEC.codec())); + event.register(Registries.MATERIAL_RULE, helper -> helper.register(HEIGHT_THRESHOLD_SELECTOR, LithostitchedRuleSources.HeightThresholdSelectorRuleSource.CODEC.codec())); + event.register(Registries.MATERIAL_RULE, helper -> helper.register(STONE_DEPTH_THRESHOLD_SELECTOR, LithostitchedRuleSources.StoneDepthThresholdSelectorRuleSource.CODEC.codec())); + + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(CLIFF, LithostitchedConditionSources.CliffConditionSource.CODEC.codec())); + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(FLAT, LithostitchedConditionSources.FlatConditionSource.CODEC.codec())); + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(FLAT_LIQUID, LithostitchedConditionSources.FlatLiquidConditionSource.CODEC.codec())); + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(LAND_TOP_LAYER, LithostitchedConditionSources.LandTopLayerConditionSource.CODEC.codec())); + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(UNDERWATER, LithostitchedConditionSources.UnderwaterConditionSource.CODEC.codec())); + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(CAVE_DEPTH, LithostitchedConditionSources.CaveDepthConditionSource.CODEC.codec())); + event.register(Registries.MATERIAL_CONDITION, helper -> helper.register(EXTENDED_BIOME, LithostitchedConditionSources.ExtendedBiomeConditionSource.CODEC.codec())); LithostitchedCommon.registerCommonBlockPredicateTypes((name, type) -> register(event, Registries.BLOCK_PREDICATE_TYPE, name, type)); LithostitchedCommon.registerCommonStateProviders((name, type) -> register(event, Registries.BLOCK_STATE_PROVIDER_TYPE, name, type)); diff --git a/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/neoforge/src/main/resources/META-INF/accesstransformer.cfg index 1ba603f..61cf6b1 100644 --- a/neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ b/neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -2,4 +2,26 @@ public-f net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator settings public net.minecraft.world.level.levelgen.DensityFunctions$EndIslandDensityFunction -public net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProviderType \ No newline at end of file +public net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProviderType + +# Non-context access necessary +public net.minecraft.world.level.levelgen.SurfaceRules$SurfaceRule +public net.minecraft.world.level.levelgen.SurfaceRules$Condition +public net.minecraft.world.level.levelgen.SurfaceRules$LazyXZCondition +public net.minecraft.world.level.levelgen.SurfaceRules$LazyYCondition +public net.minecraft.world.level.levelgen.SurfaceRules$BiomeConditionSource +public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRule +public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRule (Ljava/util/List;)V +# Access to allow working with Context +public net.minecraft.world.level.levelgen.SurfaceRules$Context +public net.minecraft.world.level.levelgen.SurfaceRules$Context blockX +public net.minecraft.world.level.levelgen.SurfaceRules$Context blockY +public net.minecraft.world.level.levelgen.SurfaceRules$Context blockZ +public net.minecraft.world.level.levelgen.SurfaceRules$Context biome +public net.minecraft.world.level.levelgen.SurfaceRules$Context chunk +public net.minecraft.world.level.levelgen.SurfaceRules$Context stoneDepthBelow +public net.minecraft.world.level.levelgen.SurfaceRules$Context randomState +public net.minecraft.world.level.levelgen.SurfaceRules$Context waterHeight +public net.minecraft.world.level.levelgen.SurfaceRules$Context stoneDepthAbove +public net.minecraft.world.level.levelgen.SurfaceRules$Context surfaceDepth +public net.minecraft.world.level.levelgen.SurfaceRules$Context getSurfaceSecondary()D \ No newline at end of file