diff --git a/src/main/java/org/betterx/bclib/api/features/BCLFeatureBuilder.java b/src/main/java/org/betterx/bclib/api/features/BCLFeatureBuilder.java index 4738e886..7ea2db24 100644 --- a/src/main/java/org/betterx/bclib/api/features/BCLFeatureBuilder.java +++ b/src/main/java/org/betterx/bclib/api/features/BCLFeatureBuilder.java @@ -5,14 +5,15 @@ import net.minecraft.data.worldgen.placement.PlacementUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.world.level.levelgen.GenerationStep.Decoration; -import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; import net.minecraft.world.level.levelgen.feature.Feature; import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; import net.minecraft.world.level.levelgen.placement.*; import net.minecraft.world.level.material.Material; import org.betterx.bclib.world.features.BCLFeature; +import org.betterx.bclib.world.features.placement.FindSolidInDirection; import org.betterx.bclib.world.features.placement.IsEmptyAboveSampledFilter; +import org.betterx.bclib.world.features.placement.MinEmptyFilter; import java.util.ArrayList; import java.util.List; @@ -188,14 +189,26 @@ public class BCLFeatureBuilder MINABLE_WITH_HAMMER = TagAPI.makeCommonBlockTag("mineable/hammer"); public static final TagKey IS_OBSIDIAN = TagAPI.makeCommonBlockTag("is_obsidian"); - public static final TagKey STALAGMITE_BASE = TagAPI.makeCommonBlockTag("stalagmite_base_blocks"); + public static final TagKey TERRAIN = TagAPI.makeCommonBlockTag("terrain"); static { TagAPI.BLOCKS.add(END_STONES, Blocks.END_STONE); @@ -47,8 +47,7 @@ public class CommonBlockTags { TagAPI.BLOCKS.add(IS_OBSIDIAN, Blocks.OBSIDIAN, Blocks.CRYING_OBSIDIAN); - TagAPI.BLOCKS.add(STALAGMITE_BASE, Blocks.DIAMOND_BLOCK); - TagAPI.BLOCKS.addOtherTags(STALAGMITE_BASE, + TagAPI.BLOCKS.addOtherTags(TERRAIN, BlockTags.DRIPSTONE_REPLACEABLE, BlockTags.BASE_STONE_OVERWORLD, NETHER_STONES, diff --git a/src/main/java/org/betterx/bclib/util/BlocksHelper.java b/src/main/java/org/betterx/bclib/util/BlocksHelper.java index bbc475fb..0c76d277 100644 --- a/src/main/java/org/betterx/bclib/util/BlocksHelper.java +++ b/src/main/java/org/betterx/bclib/util/BlocksHelper.java @@ -31,7 +31,7 @@ public class BlocksHelper { public static final int FORSE_RERENDER = 8; public static final int FLAG_IGNORE_OBSERVERS = 16; - public static final int SET_SILENT = FLAG_UPDATE_BLOCK | FLAG_IGNORE_OBSERVERS | FLAG_SEND_CLIENT_CHANGES; + public static final int SET_SILENT = FLAG_IGNORE_OBSERVERS | FLAG_SEND_CLIENT_CHANGES; public static final int SET_OBSERV = FLAG_UPDATE_BLOCK | FLAG_SEND_CLIENT_CHANGES; public static final Direction[] HORIZONTAL = makeHorizontal(); public static final Direction[] DIRECTIONS = Direction.values(); diff --git a/src/main/java/org/betterx/bclib/world/features/BlockPlaceFeature.java b/src/main/java/org/betterx/bclib/world/features/BlockPlaceFeature.java new file mode 100644 index 00000000..f9fddcf0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/BlockPlaceFeature.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.world.features; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +import com.mojang.serialization.Codec; +import org.betterx.bclib.util.BlocksHelper; + +import java.util.Optional; + +public class BlockPlaceFeature extends Feature { + public BlockPlaceFeature(Codec codec) { + super(codec); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + Optional state = ctx.config().getRandomBlock(ctx.random()); + if (state.isPresent()) + BlocksHelper.setWithoutUpdate(ctx.level(), ctx.origin(), state.get()); + return true; + } +} diff --git a/src/main/java/org/betterx/bclib/world/features/BlockPlaceFeatureConfig.java b/src/main/java/org/betterx/bclib/world/features/BlockPlaceFeatureConfig.java new file mode 100644 index 00000000..d27bbef0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/BlockPlaceFeatureConfig.java @@ -0,0 +1,59 @@ +package org.betterx.bclib.world.features; + +import net.minecraft.util.RandomSource; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; + +import java.util.List; +import java.util.Optional; + +public class BlockPlaceFeatureConfig implements FeatureConfiguration { + public static final Codec CODEC = SimpleWeightedRandomList + .wrappedCodec(BlockState.CODEC) + .comapFlatMap(BlockPlaceFeatureConfig::create, cfg -> cfg.weightedList) + .fieldOf("entries").codec(); + + private final SimpleWeightedRandomList weightedList; + + private static DataResult create(SimpleWeightedRandomList simpleWeightedRandomList) { + if (simpleWeightedRandomList.isEmpty()) { + return DataResult.error("BlockPlaceFeatureConfig with no states"); + } + return DataResult.success(new BlockPlaceFeatureConfig(simpleWeightedRandomList)); + } + + + private static SimpleWeightedRandomList convert(List states) { + var builder = SimpleWeightedRandomList.builder(); + for (BlockState s : states) builder.add(s, 1); + return builder.build(); + } + + public BlockPlaceFeatureConfig(Block block) { + this(block.defaultBlockState()); + } + + public BlockPlaceFeatureConfig(BlockState state) { + this(SimpleWeightedRandomList + .builder() + .add(state, 1) + .build()); + } + + public BlockPlaceFeatureConfig(List states) { + this(convert(states)); + } + + public BlockPlaceFeatureConfig(SimpleWeightedRandomList blocks) { + this.weightedList = blocks; + } + + public Optional getRandomBlock(RandomSource random) { + return this.weightedList.getRandomValue(random); + } +} diff --git a/src/main/java/org/betterx/bclib/world/features/ScatterFeature.java b/src/main/java/org/betterx/bclib/world/features/ScatterFeature.java index c9e9766e..db4978c3 100644 --- a/src/main/java/org/betterx/bclib/world/features/ScatterFeature.java +++ b/src/main/java/org/betterx/bclib/world/features/ScatterFeature.java @@ -105,32 +105,44 @@ public class ScatterFeature if (config.isValidBase(level.getBlockState(basePos))) { final Direction surfaceDirection = direction.getOpposite(); BlockPos.MutableBlockPos POS = new BlockPos.MutableBlockPos(); + basePos = basePos.relative(direction, 1); buildPillarWithBase(level, origin, basePos, direction, centerHeight, config, random); final double distNormalizer = (config.maxSpread * Math.sqrt(2)); - - for (int i = 0; i < 16; i++) { + final int tryCount = (int) Math.min(16, 4 * config.maxSpread * config.maxSpread); + for (int i = 0; i < tryCount; i++) { int x = origin.getX() + (int) (random.nextGaussian() * config.maxSpread); int z = origin.getZ() + (int) (random.nextGaussian() * config.maxSpread); POS.set(x, origin.getY(), z); if (BlocksHelper.findSurface(level, POS, surfaceDirection, 4, config::isValidBase)) { + int myHeight; + if (config.growWhileFree) { + myHeight = BlocksHelper.blockCount(level, + POS, + direction, + config.maxHeight, + state -> state.getMaterial().isReplaceable()); + } else { + myHeight = centerHeight; + } + POS.move(direction, 1); int dx = x - POS.getX(); int dz = z - POS.getZ(); float sizeFactor = (1 - (float) (Math.sqrt(dx * dx + dz * dz) / distNormalizer)); sizeFactor = (1 - (random.nextFloat() * config.sizeVariation)) * sizeFactor; - final int height = (int) Math.max( + myHeight = (int) Math.min(Math.max( config.minHeight, - config.minHeight + sizeFactor * (centerHeight - config.minHeight) - ); + config.minHeight + sizeFactor * (myHeight - config.minHeight) + ), config.maxHeight); buildPillarWithBase(level, POS, POS.relative(direction.getOpposite()), direction, - height, + myHeight, config, random); } @@ -147,7 +159,7 @@ public class ScatterFeature RandomSource random) { if (BlocksHelper.isFreeSpace(level, origin, direction, height, (state) -> state.is(Blocks.AIR))) { createPatchOfBaseBlocks(level, random, basePos, config); - buildPillar(level, origin, direction, height, config); + buildPillar(level, origin, direction, height, config, random); } } @@ -155,18 +167,32 @@ public class ScatterFeature BlockPos origin, Direction direction, int height, - ScatterFeatureConfig config) { + ScatterFeatureConfig config, + RandomSource random) { final BlockPos.MutableBlockPos POS = origin.mutable(); buildBaseToTipColumn(height, (blockState) -> { BlocksHelper.setWithoutUpdate(level, POS, blockState); POS.move(direction); - }, config); + }, config, random); } - protected void buildBaseToTipColumn(int totalHeight, Consumer consumer, ScatterFeatureConfig config) { - for (int size = totalHeight; size >= 0; size--) { - consumer.accept(config.createBlock(size)); + protected void buildBaseToTipColumn(int totalHeight, + Consumer consumer, + ScatterFeatureConfig config, + RandomSource random) { + for (int size = 0; size < totalHeight; size++) { + consumer.accept(config.createBlock(size, totalHeight - 1, random)); +// Block s = config.createBlock(size, totalHeight - 1, random).getBlock(); +// if (size == 0) s = Blocks.YELLOW_CONCRETE; +// else if (size == 1) s = Blocks.LIME_CONCRETE; +// else if (size == 2) s = Blocks.CYAN_CONCRETE; +// else if (size == 3) s = Blocks.LIGHT_BLUE_CONCRETE; +// else if (size == 4) s = Blocks.BLUE_CONCRETE; +// else if (size == 5) s = Blocks.PURPLE_CONCRETE; +// else if (size == 6) s = Blocks.MAGENTA_CONCRETE; +// else s = Blocks.GRAY_CONCRETE; +// consumer.accept(s.defaultBlockState()); } } @@ -217,7 +243,7 @@ public class ScatterFeature BlockPos blockPos, BlockState baseState) { BlockState blockState = levelAccessor.getBlockState(blockPos); - if (blockState.is(CommonBlockTags.STALAGMITE_BASE)) { + if (blockState.is(CommonBlockTags.TERRAIN)) { levelAccessor.setBlock(blockPos, baseState, 2); } } diff --git a/src/main/java/org/betterx/bclib/world/features/ScatterFeatureConfig.java b/src/main/java/org/betterx/bclib/world/features/ScatterFeatureConfig.java index ef8d44ca..ad5a6f62 100644 --- a/src/main/java/org/betterx/bclib/world/features/ScatterFeatureConfig.java +++ b/src/main/java/org/betterx/bclib/world/features/ScatterFeatureConfig.java @@ -1,17 +1,25 @@ package org.betterx.bclib.world.features; import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; -import com.mojang.datafixers.util.Function11; +import com.mojang.datafixers.util.Function14; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.tag.CommonBlockTags; import java.util.Optional; public abstract class ScatterFeatureConfig implements FeatureConfiguration { + public interface Instancer extends Function14, Float, Float, Float, Float, Integer, Integer, Float, Float, Float, Boolean, T> { + } + public final BlockState clusterBlock; + public final BlockState tipBlock; + public final BlockState bottomBlock; public final Optional baseState; public final float baseReplaceChance; public final float chanceOfDirectionalSpread; @@ -23,7 +31,11 @@ public abstract class ScatterFeatureConfig implements FeatureConfiguration { public final float sizeVariation; public final float floorChance; + public final boolean growWhileFree; + public ScatterFeatureConfig(BlockState clusterBlock, + BlockState tipBlock, + BlockState bottomBlock, Optional baseState, float baseReplaceChance, float chanceOfDirectionalSpread, @@ -33,8 +45,11 @@ public abstract class ScatterFeatureConfig implements FeatureConfiguration { int maxHeight, float maxSpread, float sizeVariation, - float floorChance) { + float floorChance, + boolean growWhileFree) { this.clusterBlock = clusterBlock; + this.tipBlock = tipBlock == null ? clusterBlock : tipBlock; + this.bottomBlock = bottomBlock == null ? clusterBlock : bottomBlock; this.baseState = baseState; this.baseReplaceChance = baseReplaceChance; this.chanceOfDirectionalSpread = chanceOfDirectionalSpread; @@ -45,6 +60,7 @@ public abstract class ScatterFeatureConfig implements FeatureConfiguration { this.maxSpread = maxSpread; this.sizeVariation = sizeVariation; this.floorChance = floorChance; + this.growWhileFree = growWhileFree; } @@ -54,13 +70,21 @@ public abstract class ScatterFeatureConfig implements FeatureConfiguration { public abstract boolean isValidBase(BlockState state); - public abstract BlockState createBlock(int height); + public abstract BlockState createBlock(int height, int maxHeight, RandomSource random); - public static Codec buildCodec(Function11, Float, Float, Float, Float, Integer, Integer, Float, Float, Float, T> instancer) { + public static Codec buildCodec(Instancer instancer) { return RecordCodecBuilder.create((instance) -> instance .group(BlockState.CODEC .fieldOf("cluster_block") .forGetter((T cfg) -> cfg.clusterBlock), + BlockState.CODEC + .fieldOf("tip_block") + .orElse(null) + .forGetter((T cfg) -> cfg.tipBlock), + BlockState.CODEC + .fieldOf("bottom_block") + .orElse(null) + .forGetter((T cfg) -> cfg.bottomBlock), BlockState.CODEC .optionalFieldOf("base_state") .forGetter((T cfg) -> cfg.baseState), @@ -108,10 +132,215 @@ public abstract class ScatterFeatureConfig implements FeatureConfiguration { .floatRange(0, 1) .fieldOf("floor_chance") .orElse(0.5f) - .forGetter((T cfg) -> cfg.floorChance) + .forGetter((T cfg) -> cfg.floorChance), + Codec + .BOOL + .fieldOf("grow_while_empty") + .orElse(false) + .forGetter((T cfg) -> cfg.growWhileFree) ) .apply(instance, instancer) ); } + public static class Builder { + private BlockState clusterBlock; + private BlockState tipBlock; + private BlockState bottomBlock; + private Optional baseState = Optional.empty(); + private float baseReplaceChance = 0; + private float chanceOfDirectionalSpread = 0; + private float chanceOfSpreadRadius2 = 0; + private float chanceOfSpreadRadius3 = 0; + private int minHeight = 2; + private int maxHeight = 12; + private float maxSpread = 0; + private float sizeVariation = 0; + private float floorChance = 0.5f; + private boolean growWhileFree = false; + private final Instancer instancer; + + public Builder(Instancer instancer) { + this.instancer = instancer; + } + + public static Builder start(Instancer instancer) { + return new Builder<>(instancer); + } + + public Builder block(Block b) { + return block(b.defaultBlockState()); + } + + public Builder singleBlock(Block b) { + return block(b.defaultBlockState()).heightRange(1, 1).spread(0, 0); + } + + public Builder block(BlockState s) { + this.clusterBlock = s; + if (tipBlock == null) tipBlock = s; + if (bottomBlock == null) bottomBlock = s; + return this; + } + + public Builder tipBlock(BlockState s) { + tipBlock = s; + return this; + } + + public Builder bottomBlock(BlockState s) { + bottomBlock = s; + return this; + } + + public Builder heightRange(int min, int max) { + minHeight = min; + maxHeight = max; + return this; + } + + public Builder growWhileFree() { + growWhileFree = true; + return this; + } + + public Builder minHeight(int h) { + minHeight = h; + return this; + } + + public Builder maxHeight(int h) { + maxHeight = h; + return this; + } + + public Builder generateBaseBlock(BlockState baseState) { + return generateBaseBlock(baseState, 1, 0, 0, 0); + } + + public Builder generateBaseBlock(BlockState baseState, float baseReplaceChance) { + return generateBaseBlock(baseState, baseReplaceChance, 0, 0, 0); + } + + + public Builder generateBaseBlock(BlockState baseState, + float chanceOfDirectionalSpread, + float chanceOfSpreadRadius2, + float chanceOfSpreadRadius3) { + return generateBaseBlock(baseState, + 1, + chanceOfDirectionalSpread, + chanceOfSpreadRadius2, + chanceOfSpreadRadius3); + } + + public Builder generateBaseBlock(BlockState baseState, + float baseReplaceChance, + float chanceOfDirectionalSpread, + float chanceOfSpreadRadius2, + float chanceOfSpreadRadius3) { + if (this.baseState.isPresent() && this.baseReplaceChance == 0) { + BCLib.LOGGER.error("Base generation was already selected."); + } + this.baseState = Optional.of(baseState); + this.baseReplaceChance = baseReplaceChance; + this.chanceOfDirectionalSpread = chanceOfDirectionalSpread; + this.chanceOfSpreadRadius2 = chanceOfSpreadRadius2; + this.chanceOfSpreadRadius3 = chanceOfSpreadRadius3; + return this; + } + + public Builder spread(float maxSpread, float sizeVariation) { + this.maxSpread = maxSpread; + this.sizeVariation = sizeVariation; + return this; + } + + public Builder floorChance(float chance) { + this.floorChance = chance; + return this; + } + + public Builder onFloor() { + this.floorChance = 1; + return this; + } + + public Builder onCeil() { + this.floorChance = 0; + return this; + } + + public T build() { + return instancer.apply( + this.clusterBlock, + this.tipBlock, + this.bottomBlock, + this.baseState, + this.baseReplaceChance, + this.chanceOfDirectionalSpread, + this.chanceOfSpreadRadius2, + this.chanceOfSpreadRadius3, + this.minHeight, + this.maxHeight, + this.maxSpread, + this.sizeVariation, + this.floorChance, + this.growWhileFree + ); + } + } + + public static class OnSolid extends ScatterFeatureConfig { + public static final Codec CODEC = buildCodec(OnSolid::new); + + public OnSolid(BlockState clusterBlock, + BlockState tipBlock, + BlockState bottomBlock, + Optional baseState, + float baseReplaceChance, + float chanceOfDirectionalSpread, + float chanceOfSpreadRadius2, + float chanceOfSpreadRadius3, + int minHeight, + int maxHeight, + float maxSpread, + float sizeVariation, + float floorChance, + boolean growWhileFree) { + super(clusterBlock, + tipBlock, + bottomBlock, + baseState, + baseReplaceChance, + chanceOfDirectionalSpread, + chanceOfSpreadRadius2, + chanceOfSpreadRadius3, + minHeight, + maxHeight, + maxSpread, + sizeVariation, + floorChance, + growWhileFree); + } + + public static Builder startOnSolid() { + return Builder.start(OnSolid::new); + } + + + @Override + public boolean isValidBase(BlockState state) { + return state.is(CommonBlockTags.TERRAIN); + } + + @Override + public BlockState createBlock(int height, int maxHeight, RandomSource random) { + if (height == 0) return this.bottomBlock; + return height == maxHeight + ? this.tipBlock + : this.clusterBlock; + } + } + } diff --git a/src/main/java/org/betterx/bclib/world/features/TemplateFeatureConfig.java b/src/main/java/org/betterx/bclib/world/features/TemplateFeatureConfig.java index 63750ed1..06325b28 100644 --- a/src/main/java/org/betterx/bclib/world/features/TemplateFeatureConfig.java +++ b/src/main/java/org/betterx/bclib/world/features/TemplateFeatureConfig.java @@ -16,7 +16,7 @@ public class TemplateFeatureConfig implements FeatureConfiguration { .group( ExtraCodecs.nonEmptyList(StructureWorldNBT.CODEC.listOf()) .fieldOf("structures") - .forGetter((TemplateFeatureConfig ruinedPortalStructure) -> ruinedPortalStructure.structures) + .forGetter((TemplateFeatureConfig cfg) -> cfg.structures) ) .apply(instance, TemplateFeatureConfig::new) ); diff --git a/src/main/java/org/betterx/bclib/world/features/placement/Extend.java b/src/main/java/org/betterx/bclib/world/features/placement/Extend.java new file mode 100644 index 00000000..ccf76dd9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/placement/Extend.java @@ -0,0 +1,57 @@ +package org.betterx.bclib.world.features.placement; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.util.valueproviders.IntProvider; +import net.minecraft.util.valueproviders.UniformInt; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public class Extend extends PlacementModifier { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Direction.CODEC + .fieldOf("direction") + .orElse(Direction.DOWN) + .forGetter(cfg -> cfg.direction), + IntProvider.codec(0, 16) + .fieldOf("length") + .orElse(UniformInt.of(0, 3)) + .forGetter(cfg -> cfg.length) + ) + .apply(instance, Extend::new)); + + private final Direction direction; + private final IntProvider length; + + public Extend(Direction direction, IntProvider length) { + this.direction = direction; + this.length = length; + } + + @Override + public Stream getPositions(PlacementContext placementContext, + RandomSource random, + BlockPos blockPos) { + final int count = length.sample(random); + List pos = new ArrayList<>(count); + for (int y = 0; y < count; y++) { + pos.add(blockPos.relative(direction, y + 1)); + } + return pos.stream(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.EXTEND; + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/world/features/placement/FindSolidInDirection.java b/src/main/java/org/betterx/bclib/world/features/placement/FindSolidInDirection.java new file mode 100644 index 00000000..98e973bc --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/placement/FindSolidInDirection.java @@ -0,0 +1,74 @@ +package org.betterx.bclib.world.features.placement; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.betterx.bclib.api.tag.CommonBlockTags; +import org.betterx.bclib.util.BlocksHelper; + +import java.util.stream.Stream; + +public class FindSolidInDirection extends PlacementModifier { + + protected static final FindSolidInDirection DOWN = new FindSolidInDirection(Direction.DOWN, 12); + protected static final FindSolidInDirection UP = new FindSolidInDirection(Direction.UP, 12); + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Direction.CODEC.fieldOf("dir").orElse(Direction.DOWN).forGetter((p) -> p.direction), + Codec.intRange(1, 32).fieldOf("dist").orElse(12).forGetter((p) -> p.maxSearchDistance) + ) + .apply(instance, FindSolidInDirection::new)); + + public FindSolidInDirection(Direction direction, int maxSearchDistance) { + this.direction = direction; + this.maxSearchDistance = maxSearchDistance; + } + + public static PlacementModifier down() { + return DOWN; + } + + public static PlacementModifier up() { + return UP; + } + + public static PlacementModifier down(int dist) { + if (dist == DOWN.maxSearchDistance) return DOWN; + return new FindSolidInDirection(Direction.DOWN, dist); + } + + public static PlacementModifier up(int dist) { + if (dist == UP.maxSearchDistance) return UP; + return new FindSolidInDirection(Direction.UP, dist); + } + + + @Override + public Stream getPositions(PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos) { + BlockPos.MutableBlockPos POS = blockPos.mutable(); + if (BlocksHelper.findSurface(placementContext.getLevel(), + POS, + direction, + maxSearchDistance, + state -> state.is(CommonBlockTags.TERRAIN)) + ) return Stream.of(POS); + + return Stream.of(); + } + + private final Direction direction; + private final int maxSearchDistance; + + @Override + public PlacementModifierType type() { + return PlacementModifiers.SOLID_IN_DIR; + } +} diff --git a/src/main/java/org/betterx/bclib/world/features/placement/IsBasin.java b/src/main/java/org/betterx/bclib/world/features/placement/IsBasin.java new file mode 100644 index 00000000..73e7f884 --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/placement/IsBasin.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.world.features.placement; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import java.util.List; + +public class IsBasin extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + ExtraCodecs.nonEmptyList(BlockState.CODEC.listOf()) + .fieldOf("blocks") + .forGetter(cfg -> cfg.blocks) + ) + .apply(instance, IsBasin::new)); + + private final List blocks; + + public IsBasin(List blocks) { + this.blocks = blocks; + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) { + BlockState bs = ctx.getLevel().getBlockState(pos); + return blocks.stream().map(b -> b.getBlock()).anyMatch(b -> bs.is(b)); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.IS_BASIN; + } +} diff --git a/src/main/java/org/betterx/bclib/world/features/placement/MinEmptyFilter.java b/src/main/java/org/betterx/bclib/world/features/placement/MinEmptyFilter.java index 3c8656d8..5ee6d1ad 100644 --- a/src/main/java/org/betterx/bclib/world/features/placement/MinEmptyFilter.java +++ b/src/main/java/org/betterx/bclib/world/features/placement/MinEmptyFilter.java @@ -13,40 +13,48 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import org.betterx.bclib.util.BlocksHelper; public class MinEmptyFilter extends PlacementFilter { - private static MinEmptyFilter DOWN = new MinEmptyFilter(Direction.DOWN, 12); + private static MinEmptyFilter DOWN = new MinEmptyFilter(Direction.DOWN, 2); + private static MinEmptyFilter UP = new MinEmptyFilter(Direction.UP, 2); public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance .group( Direction.CODEC.fieldOf("dir").orElse(Direction.DOWN).forGetter((p) -> p.direction), - Codec.intRange(1, 32).fieldOf("dist").orElse(12).forGetter((p) -> p.maxSearchDistance) + Codec.intRange(1, 32).fieldOf("dist").orElse(12).forGetter((p) -> p.distance) ) .apply(instance, MinEmptyFilter::new)); private final Direction direction; - private final int maxSearchDistance; + private final int distance; - protected MinEmptyFilter(Direction direction, int maxSearchDistance) { + protected MinEmptyFilter(Direction direction, int distance) { this.direction = direction; - this.maxSearchDistance = maxSearchDistance; + this.distance = distance; } - public PlacementModifier down() { + public static PlacementModifier down() { return DOWN; } - public PlacementModifier down(int dist) { + public static PlacementModifier down(int dist) { return new MinEmptyFilter(Direction.DOWN, dist); } + public static PlacementModifier up() { + return UP; + } + + public static PlacementModifier up(int dist) { + return new MinEmptyFilter(Direction.UP, dist); + } + @Override protected boolean shouldPlace(PlacementContext ctx, RandomSource randomSource, BlockPos pos) { - int h = BlocksHelper.blockCount( + return BlocksHelper.isFreeSpace( ctx.getLevel(), pos.relative(direction), direction, - maxSearchDistance, + distance - 1, state -> state.getMaterial().isReplaceable() ); - return false; } @Override diff --git a/src/main/java/org/betterx/bclib/world/features/placement/Offset.java b/src/main/java/org/betterx/bclib/world/features/placement/Offset.java new file mode 100644 index 00000000..3526d34c --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/placement/Offset.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.world.features.placement; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import java.util.stream.Stream; + +public class Offset extends PlacementModifier { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Vec3i.CODEC + .fieldOf("blocks") + .forGetter(cfg -> cfg.offset) + ) + .apply(instance, Offset::new)); + + private final Vec3i offset; + + public Offset(Vec3i offset) { + this.offset = offset; + } + + @Override + public Stream getPositions(PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos) { + return Stream.of(blockPos.offset(offset)); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.OFFSET; + } +} diff --git a/src/main/java/org/betterx/bclib/world/features/placement/PlacementModifiers.java b/src/main/java/org/betterx/bclib/world/features/placement/PlacementModifiers.java index a7e200ee..4d5f5cff 100644 --- a/src/main/java/org/betterx/bclib/world/features/placement/PlacementModifiers.java +++ b/src/main/java/org/betterx/bclib/world/features/placement/PlacementModifiers.java @@ -17,6 +17,26 @@ public class PlacementModifiers { "min_empty_filter", MinEmptyFilter.CODEC); + public static final PlacementModifierType SOLID_IN_DIR = register( + "solid_in_dir", + FindSolidInDirection.CODEC); + + public static final PlacementModifierType STENCIL = register( + "stencil", + Stencil.CODEC); + + public static final PlacementModifierType IS_BASIN = register( + "is_basin", + IsBasin.CODEC); + + public static final PlacementModifierType OFFSET = register( + "offset", + Offset.CODEC); + + public static final PlacementModifierType EXTEND = register( + "extend", + Extend.CODEC); + private static

PlacementModifierType

register(String path, Codec

codec) { return register(BCLib.makeID(path), codec); diff --git a/src/main/java/org/betterx/bclib/world/features/placement/Stencil.java b/src/main/java/org/betterx/bclib/world/features/placement/Stencil.java new file mode 100644 index 00000000..4dedd640 --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/features/placement/Stencil.java @@ -0,0 +1,333 @@ +package org.betterx.bclib.world.features.placement; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +public class Stencil extends PlacementModifier { + public static final Codec CODEC; + private static final Boolean[] BN_STENCIL; + private final List stencil; + private static final Stencil DEFAULT; + + private static List convert(Boolean[] s) { + return Arrays.stream(s).toList(); + } + + public Stencil(Boolean[] stencil) { + this(convert(stencil)); + } + + public Stencil(List stencil) { + this.stencil = stencil; + } + + public static Stencil basic() { + return DEFAULT; + } + + + @Override + public Stream getPositions(PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos) { + List pos = new ArrayList<>(16 * 16); + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + if (stencil.get(x << 4 | y)) { + pos.add(blockPos.offset(x, 0, y)); + } + } + } + + return pos.stream(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.STENCIL; + } + + static { + BN_STENCIL = new Boolean[]{ + false, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + true, + false, + false, + true, + true, + true, + false, + false, + false, + true, + true, + false, + false, + false, + true, + false, + false, + true, + true, + true, + false, + false, + true, + true, + true, + true, + false, + true, + true, + true, + true, + false, + false, + false, + true, + true, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + true, + true, + true, + true, + false, + false, + false, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + true, + true, + false, + true, + true, + false, + false, + false, + true, + false, + false, + true, + false, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + true, + false, + false, + true, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + true, + true, + false, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + true, + false, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + true, + false, + false, + false, + true, + false, + false, + false, + false, + false, + true, + true, + false, + false + }; + + DEFAULT = new Stencil(BN_STENCIL); + CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + ExtraCodecs.nonEmptyList(Codec.BOOL.listOf()) + .fieldOf("structures") + .orElse(convert(BN_STENCIL)) + .forGetter((Stencil a) -> a.stencil) + ) + .apply(instance, Stencil::new) + ); + } +}