Some placement filters

This commit is contained in:
Frank 2022-06-02 11:17:07 +02:00
parent 4321017c25
commit aa4133fad2
12 changed files with 318 additions and 47 deletions

View file

@ -22,6 +22,7 @@ import org.betterx.bclib.recipes.CraftingRecipes;
import org.betterx.bclib.registry.BaseBlockEntities;
import org.betterx.bclib.registry.BaseRegistry;
import org.betterx.bclib.util.Logger;
import org.betterx.bclib.world.features.placement.PlacementModifiers;
import org.betterx.bclib.world.generator.BCLibEndBiomeSource;
import org.betterx.bclib.world.generator.BCLibNetherBiomeSource;
import org.betterx.bclib.world.generator.GeneratorOptions;
@ -50,16 +51,17 @@ public class BCLib implements ModInitializer {
AnvilRecipe.register();
DataExchangeAPI.registerDescriptors(List.of(
HelloClient.DESCRIPTOR,
HelloServer.DESCRIPTOR,
RequestFiles.DESCRIPTOR,
SendFiles.DESCRIPTOR,
Chunker.DESCRIPTOR
)
);
HelloClient.DESCRIPTOR,
HelloServer.DESCRIPTOR,
RequestFiles.DESCRIPTOR,
SendFiles.DESCRIPTOR,
Chunker.DESCRIPTOR
)
);
BCLibPatch.register();
TemplatePiece.ensureStaticInitialization();
PlacementModifiers.ensureStaticInitialization();
Configs.save();
if (isDevEnvironment()) {
Biome.BiomeBuilder builder = new Biome.BiomeBuilder()

View file

@ -1,14 +1,18 @@
package org.betterx.bclib.api.features;
import net.minecraft.core.Direction;
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.IsEmptyAboveSampledFilter;
import java.util.ArrayList;
import java.util.List;
@ -132,14 +136,79 @@ public class BCLFeatureBuilder<FC extends FeatureConfiguration, F extends Featur
return modifier(InSquarePlacement.spread());
}
public BCLFeatureBuilder distanceToTopAndBottom10() {
/**
* Select random height that is 10 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight10FromFloorCeil() {
return modifier(PlacementUtils.RANGE_10_10);
}
public BCLFeatureBuilder distanceToTopAndBottom4() {
/**
* Select random height that is 4 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight4FromFloorCeil() {
return modifier(PlacementUtils.RANGE_4_4);
}
/**
* Select random height that is 8 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight8FromFloorCeil() {
return modifier(PlacementUtils.RANGE_8_8);
}
/**
* Select random height that is above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight() {
return modifier(PlacementUtils.FULL_RANGE);
}
public BCLFeatureBuilder isEmptyAbove4() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove4());
}
public BCLFeatureBuilder isEmptyAbove(int d1, int d2) {
return modifier(new IsEmptyAboveSampledFilter(d1, d2));
}
/**
* Cast a downward ray with max {@code distance} length to find the next solid Block.
*
* @param distance The maximum search Distance
* @return The instance it was called on
* @see #findSolidSurface(Direction, int) for Details
*/
public BCLFeatureBuilder findSolidFloor(int distance) {
return findSolidSurface(Direction.DOWN, distance);
}
/**
* Cast a ray with max {@code distance} length to find the next solid Block. The ray will travel through replaceable
* Blocks (see {@link Material#isReplaceable()}) and will be accepted if it hits a solid one
* (see {@link Material#isSolid()} ()})
*
* @param dir The direction the ray is cast
* @param distance The maximum search Distance
* @return The instance it was called on
* @see #findSolidSurface(Direction, int) for Details
*/
public BCLFeatureBuilder findSolidSurface(Direction dir, int distance) {
return modifier(EnvironmentScanPlacement.scanningFor(dir,
BlockPredicate.solid(),
BlockPredicate.replaceable(),
distance));
}
public BCLFeatureBuilder heightmap() {
return modifier(PlacementUtils.HEIGHTMAP);
}

View file

@ -0,0 +1,11 @@
package org.betterx.bclib.interfaces;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
public interface BCLPlacementContext {
Rotation bcl_getRotation();
void bcl_setRotation(Rotation bcl_rotation);
Mirror bcl_getMirror();
void bcl_setMirror(Mirror bcl_mirror);
}

View file

@ -0,0 +1,34 @@
package org.betterx.bclib.mixin.common;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.placement.PlacementContext;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(PlacementContext.class)
public class PlacementContextMixin implements org.betterx.bclib.interfaces.BCLPlacementContext {
private Rotation bcl_rotation = Rotation.NONE;
private Mirror bcl_mirror = Mirror.NONE;
@Override
public Rotation bcl_getRotation() {
return bcl_rotation;
}
@Override
public void bcl_setRotation(Rotation bcl_rotation) {
this.bcl_rotation = bcl_rotation;
}
@Override
public Mirror bcl_getMirror() {
return bcl_mirror;
}
@Override
public void bcl_setMirror(Mirror bcl_mirror) {
this.bcl_mirror = bcl_mirror;
}
}

View file

@ -245,6 +245,22 @@ public class BlocksHelper {
return true;
}
public static int blockCount(LevelAccessor level,
BlockPos startPos,
Direction dir,
int length,
Predicate<BlockState> freeSurface) {
MutableBlockPos POS = startPos.mutable();
for (int len = 1; len < length; len++) {
POS.move(dir, 1);
if (!freeSurface.test(level.getBlockState(POS))) {
return len - 1;
}
}
return length;
}
public static boolean isLava(BlockState state) {
return state.getFluidState().getType() instanceof LavaFluid;
}

View file

@ -2,6 +2,7 @@ package org.betterx.bclib.world.features;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.resources.ResourceLocation;
@ -25,6 +26,8 @@ import org.betterx.bclib.api.features.BCLFeatureBuilder;
import org.betterx.bclib.api.tag.CommonBlockTags;
import org.betterx.bclib.util.BlocksHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@ -35,28 +38,32 @@ public class ScatterFeature<FC extends ScatterFeatureConfig>
int minPerChunk,
int maxPerChunk,
T cfg,
Feature<T> inlineFeatures) {
SimpleRandomFeatureConfiguration configuration = new SimpleRandomFeatureConfiguration(HolderSet.direct(
PlacementUtils.inlinePlaced(inlineFeatures,
cfg,
EnvironmentScanPlacement.scanningFor(Direction.DOWN,
BlockPredicate.solid(),
BlockPredicate.ONLY_IN_AIR_PREDICATE,
12),
RandomOffsetPlacement.vertical(ConstantInt.of(1))),
PlacementUtils.inlinePlaced(inlineFeatures,
cfg,
EnvironmentScanPlacement.scanningFor(Direction.UP,
BlockPredicate.solid(),
BlockPredicate.ONLY_IN_AIR_PREDICATE,
12),
RandomOffsetPlacement.vertical(ConstantInt.of(-1)))));
Feature<T> inlineFeature) {
List<Holder<PlacedFeature>> set = new ArrayList<>(2);
if (cfg.floorChance > 0) set.add(PlacementUtils.inlinePlaced(inlineFeature,
cfg,
EnvironmentScanPlacement.scanningFor(Direction.DOWN,
BlockPredicate.solid(),
BlockPredicate.ONLY_IN_AIR_PREDICATE,
12),
RandomOffsetPlacement.vertical(ConstantInt.of(1))));
if (cfg.floorChance < 1) {
set.add(PlacementUtils.inlinePlaced(inlineFeature,
cfg,
EnvironmentScanPlacement.scanningFor(Direction.UP,
BlockPredicate.solid(),
BlockPredicate.ONLY_IN_AIR_PREDICATE,
12),
RandomOffsetPlacement.vertical(ConstantInt.of(-1))));
}
SimpleRandomFeatureConfiguration configuration = new SimpleRandomFeatureConfiguration(HolderSet.direct(set));
return BCLFeatureBuilder.start(location, SIMPLE_RANDOM_SELECTOR)
.decoration(GenerationStep.Decoration.VEGETAL_DECORATION)
.modifier(CountPlacement.of(UniformInt.of(minPerChunk, maxPerChunk)))
.modifier(InSquarePlacement.spread())
.distanceToTopAndBottom4()
.randomHeight4FromFloorCeil()
.modifier(CountPlacement.of(UniformInt.of(2, 5)))
.modifier(RandomOffsetPlacement.of(
ClampedNormalInt.of(0.0f, 2.0f, -6, 6),

View file

@ -1,15 +1,10 @@
package org.betterx.bclib.world.features;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.placement.BiomeFilter;
import net.minecraft.world.level.levelgen.placement.EnvironmentScanPlacement;
import com.mojang.serialization.Codec;
import org.betterx.bclib.api.features.BCLFeatureBuilder;
@ -29,30 +24,28 @@ public class TemplateFeature<FC extends TemplateFeatureConfig> extends Feature<F
return BCLFeatureBuilder
.start(location, INSTANCE)
.decoration(GenerationStep.Decoration.SURFACE_STRUCTURES)
.squarePlacement()
.distanceToTopAndBottom10()
.modifier(EnvironmentScanPlacement.scanningFor(Direction.DOWN,
BlockPredicate.solid(),
BlockPredicate.matchesBlocks(Blocks.AIR,
Blocks.WATER,
Blocks.LAVA),
12))
.modifier(BiomeFilter.biome())
.oncePerChunks(onceEveryChunk)
.oncePerChunks(onceEveryChunk) //discard neighboring chunks
.count(16) //try 16 placements in chunk
.squarePlacement() //randomize x/z in chunk
.randomHeight10FromFloorCeil() //randomize height 10 above and 10 below max vertical
.findSolidFloor(12) //cast downward ray to find solid surface
.isEmptyAbove4() //make sure we have 4 free blocks above
.onlyInBiome() //ensure that we still are in the correct biome
.buildAndRegister(configuration);
}
public static <T extends TemplateFeatureConfig> BCLFeature createAndRegister(ResourceLocation location,
TemplateFeatureConfig configuration,
int count) {
return BCLFeatureBuilder
.start(location, INSTANCE)
.decoration(GenerationStep.Decoration.SURFACE_STRUCTURES)
.count(count)
.squarePlacement()
.distanceToTopAndBottom10()
.randomHeight10FromFloorCeil()
.findSolidFloor(12) //cast downward ray to find solid surface
.isEmptyAbove4()
.onlyInBiome()
.buildAndRegister(configuration);
}

View file

@ -0,0 +1,51 @@
package org.betterx.bclib.world.features.placement;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
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;
/**
* Tests if there is air at two locations above the tested block position
*/
public class IsEmptyAboveSampledFilter extends PlacementFilter {
private static final IsEmptyAboveSampledFilter DEFAULT = new IsEmptyAboveSampledFilter(4, 2);
public static final Codec<IsEmptyAboveSampledFilter> CODEC = RecordCodecBuilder.create((instance) -> instance
.group(
Codec.intRange(1, 32).fieldOf("d1").orElse(2).forGetter((p) -> p.distance1),
Codec.intRange(1, 32).fieldOf("d2").orElse(4).forGetter((p) -> p.distance1)
)
.apply(instance, IsEmptyAboveSampledFilter::new));
public static PlacementFilter emptyAbove4() {
return DEFAULT;
}
public IsEmptyAboveSampledFilter(int d1, int d2) {
this.distance1 = d1;
this.distance2 = d2;
}
private final int distance1;
private final int distance2;
@Override
protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) {
WorldGenLevel level = ctx.getLevel();
if (level.isEmptyBlock(pos.above(distance1)) && level.isEmptyBlock(pos.above(distance2))) {
return true;
}
return false;
}
@Override
public PlacementModifierType<?> type() {
return PlacementModifiers.IS_EMPTY_ABOVE_SAMPLED_FILTER;
}
}

View file

@ -0,0 +1,56 @@
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.PlacementFilter;
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.util.BlocksHelper;
public class MinEmptyFilter extends PlacementFilter {
private static MinEmptyFilter DOWN = new MinEmptyFilter(Direction.DOWN, 12);
public static final Codec<MinEmptyFilter> 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, MinEmptyFilter::new));
private final Direction direction;
private final int maxSearchDistance;
protected MinEmptyFilter(Direction direction, int maxSearchDistance) {
this.direction = direction;
this.maxSearchDistance = maxSearchDistance;
}
public PlacementModifier down() {
return DOWN;
}
public PlacementModifier down(int dist) {
return new MinEmptyFilter(Direction.DOWN, dist);
}
@Override
protected boolean shouldPlace(PlacementContext ctx, RandomSource randomSource, BlockPos pos) {
int h = BlocksHelper.blockCount(
ctx.getLevel(),
pos.relative(direction),
direction,
maxSearchDistance,
state -> state.getMaterial().isReplaceable()
);
return false;
}
@Override
public PlacementModifierType<?> type() {
return PlacementModifiers.MIN_EMPTY_FILTER;
}
}

View file

@ -0,0 +1,34 @@
package org.betterx.bclib.world.features.placement;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import net.minecraft.world.level.levelgen.placement.PlacementModifierType;
import com.mojang.serialization.Codec;
import org.betterx.bclib.BCLib;
public class PlacementModifiers {
public static final PlacementModifierType<IsEmptyAboveSampledFilter> IS_EMPTY_ABOVE_SAMPLED_FILTER = register(
"is_empty_above_sampled_filter",
IsEmptyAboveSampledFilter.CODEC);
public static final PlacementModifierType<MinEmptyFilter> MIN_EMPTY_FILTER = register(
"min_empty_filter",
MinEmptyFilter.CODEC);
private static <P extends PlacementModifier> PlacementModifierType<P> register(String path, Codec<P> codec) {
return register(BCLib.makeID(path), codec);
}
public static <P extends PlacementModifier> PlacementModifierType<P> register(ResourceLocation location,
Codec<P> codec) {
return Registry.register(Registry.PLACEMENT_MODIFIERS, location, () -> codec);
}
public static void ensureStaticInitialization() {
}
}

View file

@ -5,7 +5,7 @@ import net.minecraft.util.StringRepresentable;
import com.mojang.serialization.Codec;
public enum StructurePlacementType implements StringRepresentable {
FLOOR, WALL, CEIL, LAVA, UNDER, FLOOR_FREE_ABOVE;
FLOOR, WALL, CEIL, LAVA, UNDER;
public static final Codec<StructurePlacementType> CODEC = StringRepresentable.fromEnum(StructurePlacementType::values);

View file

@ -104,8 +104,6 @@ public class StructureWorldNBT extends StructureNBT {
return canGenerateUnder(level, pos, rotation);
else if (type == StructurePlacementType.CEIL)
return canGenerateCeil(level, pos, rotation);
else if (type == StructurePlacementType.FLOOR_FREE_ABOVE)
return canGenerateFloorFreeAbove(level, pos, rotation);
else
return false;
}