Moved Classes

This commit is contained in:
Frank 2022-06-03 01:34:49 +02:00
parent 35ce65674b
commit 49edee32a8
28 changed files with 31 additions and 52 deletions

View file

@ -29,6 +29,7 @@ import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.betterx.bclib.api.features.BCLFeature;
import org.betterx.bclib.api.structures.BCLStructure;
import org.betterx.bclib.api.surface.SurfaceRuleBuilder;
import org.betterx.bclib.entity.BCLEntityWrapper;
@ -37,7 +38,6 @@ import org.betterx.bclib.util.CollectionsUtil;
import org.betterx.bclib.util.ColorUtil;
import org.betterx.bclib.util.Pair;
import org.betterx.bclib.util.TriFunction;
import org.betterx.bclib.world.features.BCLFeature;
import java.util.ArrayList;
import java.util.List;

View file

@ -42,6 +42,7 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.mutable.MutableInt;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.features.BCLFeature;
import org.betterx.bclib.api.tag.CommonBiomeTags;
import org.betterx.bclib.api.tag.TagAPI;
import org.betterx.bclib.interfaces.BiomeSourceAccessor;
@ -51,7 +52,6 @@ import org.betterx.bclib.interfaces.SurfaceRuleProvider;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.bclib.mixin.common.MobSpawnSettingsAccessor;
import org.betterx.bclib.util.CollectionsUtil;
import org.betterx.bclib.world.features.BCLFeature;
import java.util.List;
import java.util.Map;

View file

@ -9,8 +9,6 @@ import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguratio
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest;
import org.betterx.bclib.world.features.BCLFeature;
public class BCLCommonFeatures {
/**
* Will create a basic plant feature.

View file

@ -0,0 +1,127 @@
package org.betterx.bclib.api.features;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.data.worldgen.features.FeatureUtils;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.features.config.BlockPlaceFeatureConfig;
import org.betterx.bclib.api.features.config.ScatterFeatureConfig;
import java.util.Map.Entry;
import java.util.Optional;
public class BCLFeature {
public static final Feature<ScatterFeatureConfig.OnSolid> SCATTER_ON_SOLID = register(
BCLib.makeID("scatter_on_solid"),
new ScatterFeature<>(ScatterFeatureConfig.OnSolid.CODEC)
);
public static final Feature<BlockPlaceFeatureConfig> PLACE_BLOCK = register(
BCLib.makeID("place_block"),
new BlockPlaceFeature<>(BlockPlaceFeatureConfig.CODEC)
);
private final Holder<PlacedFeature> placedFeature;
private final Decoration featureStep;
private final Feature<?> feature;
public <FC extends FeatureConfiguration, F extends Feature<FC>> BCLFeature(ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
PlacementModifier[] modifiers) {
this(id, feature, featureStep, buildPlacedFeature(id, feature, configuration, modifiers));
}
public BCLFeature(ResourceLocation id,
Feature<?> feature,
Decoration featureStep,
Holder<PlacedFeature> placedFeature) {
this.placedFeature = placedFeature;
this.featureStep = featureStep;
this.feature = feature;
if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) {
Registry.register(BuiltinRegistries.PLACED_FEATURE, id, placedFeature.value());
}
if (!Registry.FEATURE.containsKey(id) && !containsObj(Registry.FEATURE, feature)) {
Registry.register(Registry.FEATURE, id, feature);
}
}
private static <FC extends FeatureConfiguration, F extends Feature<FC>> Holder<PlacedFeature> buildPlacedFeature(
ResourceLocation id,
F feature,
FC configuration,
PlacementModifier[] modifiers) {
Holder<ConfiguredFeature<?, ?>> configuredFeature;
if (!BuiltinRegistries.CONFIGURED_FEATURE.containsKey(id)) {
configuredFeature = (Holder<ConfiguredFeature<?, ?>>) (Object) FeatureUtils.register(id.toString(),
feature,
configuration);
} else {
configuredFeature = BuiltinRegistries.CONFIGURED_FEATURE
.getHolder(ResourceKey.create(BuiltinRegistries.CONFIGURED_FEATURE.key(),
id))
.orElseThrow();
}
if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) {
return PlacementUtils.register(id.toString(), configuredFeature, modifiers);
} else {
return BuiltinRegistries.PLACED_FEATURE.getHolder(ResourceKey.create(BuiltinRegistries.PLACED_FEATURE.key(),
id)).orElseThrow();
}
}
private static <E> boolean containsObj(Registry<E> registry, E obj) {
Optional<Entry<ResourceKey<E>, E>> optional = registry
.entrySet()
.stream()
.filter(entry -> entry.getValue() == obj)
.findAny();
return optional.isPresent();
}
public static <C extends FeatureConfiguration, F extends Feature<C>> F register(ResourceLocation string,
F feature) {
return Registry.register(Registry.FEATURE, string, feature);
}
/**
* Get raw feature.
*
* @return {@link Feature}.
*/
public Feature<?> getFeature() {
return feature;
}
/**
* Get configured feature.
*
* @return {@link PlacedFeature}.
*/
public Holder<PlacedFeature> getPlacedFeature() {
return placedFeature;
}
/**
* Get feature decoration step.
*
* @return {@link Decoration}.
*/
public Decoration getDecoration() {
return featureStep;
}
}

View file

@ -13,8 +13,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfigur
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.*;
import org.betterx.bclib.api.features.placement.*;
import java.util.ArrayList;
import java.util.List;

View file

@ -0,0 +1,25 @@
package org.betterx.bclib.api.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.api.features.config.BlockPlaceFeatureConfig;
import org.betterx.bclib.util.BlocksHelper;
import java.util.Optional;
public class BlockPlaceFeature<FC extends BlockPlaceFeatureConfig> extends Feature<FC> {
public BlockPlaceFeature(Codec<FC> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<FC> ctx) {
Optional<BlockState> state = ctx.config().getRandomBlock(ctx.random());
if (state.isPresent())
BlocksHelper.setWithoutUpdate(ctx.level(), ctx.origin(), state.get());
return true;
}
}

View file

@ -0,0 +1,45 @@
package org.betterx.bclib.api.features;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap.Types;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import org.betterx.bclib.util.BlocksHelper;
public abstract class DefaultFeature extends Feature<NoneFeatureConfiguration> {
protected static final BlockState AIR = Blocks.AIR.defaultBlockState();
protected static final BlockState WATER = Blocks.WATER.defaultBlockState();
public DefaultFeature() {
super(NoneFeatureConfiguration.CODEC);
}
public static int getYOnSurface(WorldGenLevel world, int x, int z) {
return world.getHeight(Types.WORLD_SURFACE, x, z);
}
public static int getYOnSurfaceWG(WorldGenLevel world, int x, int z) {
return world.getHeight(Types.WORLD_SURFACE_WG, x, z);
}
public static BlockPos getPosOnSurface(WorldGenLevel world, BlockPos pos) {
return world.getHeightmapPos(Types.WORLD_SURFACE, pos);
}
public static BlockPos getPosOnSurfaceWG(WorldGenLevel world, BlockPos pos) {
return world.getHeightmapPos(Types.WORLD_SURFACE_WG, pos);
}
public static BlockPos getPosOnSurfaceRaycast(WorldGenLevel world, BlockPos pos) {
return getPosOnSurfaceRaycast(world, pos, 256);
}
public static BlockPos getPosOnSurfaceRaycast(WorldGenLevel world, BlockPos pos, int dist) {
int h = BlocksHelper.downRay(world, pos, dist);
return pos.below(h);
}
}

View file

@ -0,0 +1,82 @@
package org.betterx.bclib.api.features;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import org.betterx.bclib.util.StructureHelper;
import java.util.List;
public class ListFeature extends NBTFeature {
private final List<StructureInfo> list;
private StructureInfo selected;
public ListFeature(List<StructureInfo> list, BlockState defaultBlock) {
super(defaultBlock);
this.list = list;
}
@Override
protected StructureTemplate getStructure(WorldGenLevel world, BlockPos pos, RandomSource random) {
selected = list.get(random.nextInt(list.size()));
return selected.getStructure();
}
@Override
protected boolean canSpawn(WorldGenLevel world, BlockPos pos, RandomSource random) {
int cx = pos.getX() >> 4;
int cz = pos.getZ() >> 4;
return ((cx + cz) & 1) == 0 && pos.getY() > 58;// && world.getBlockState(pos.below()).is(EndTags.GEN_TERRAIN);
}
@Override
protected Rotation getRotation(WorldGenLevel world, BlockPos pos, RandomSource random) {
return Rotation.getRandom(random);
}
@Override
protected Mirror getMirror(WorldGenLevel world, BlockPos pos, RandomSource random) {
return Mirror.values()[random.nextInt(3)];
}
@Override
protected int getYOffset(StructureTemplate structure, WorldGenLevel world, BlockPos pos, RandomSource random) {
return selected.offsetY;
}
@Override
protected TerrainMerge getTerrainMerge(WorldGenLevel world, BlockPos pos, RandomSource random) {
return selected.terrainMerge;
}
@Override
protected void addStructureData(StructurePlaceSettings data) {
}
public static final class StructureInfo {
public final TerrainMerge terrainMerge;
public final String structurePath;
public final int offsetY;
private StructureTemplate structure;
public StructureInfo(String structurePath, int offsetY, TerrainMerge terrainMerge) {
this.terrainMerge = terrainMerge;
this.structurePath = structurePath;
this.offsetY = offsetY;
}
public StructureTemplate getStructure() {
if (structure == null) {
structure = StructureHelper.readStructure(structurePath);
}
return structure;
}
}
}

View file

@ -0,0 +1,235 @@
package org.betterx.bclib.api.features;
import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockPos.MutableBlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import org.betterx.bclib.api.biomes.BiomeAPI;
import org.betterx.bclib.api.tag.CommonBlockTags;
import org.betterx.bclib.util.BlocksHelper;
import org.betterx.bclib.world.processors.DestructionStructureProcessor;
import java.io.IOException;
import java.io.InputStream;
//TODO: 1.19 Check if we can merge this with the new TemplateFeature!
public abstract class NBTFeature extends DefaultFeature {
private final BlockState defaultBlock;
public NBTFeature(BlockState defaultBlock) {
this.defaultBlock = defaultBlock;
}
protected static final DestructionStructureProcessor DESTRUCTION = new DestructionStructureProcessor();
protected abstract StructureTemplate getStructure(WorldGenLevel world, BlockPos pos, RandomSource random);
protected abstract boolean canSpawn(WorldGenLevel world, BlockPos pos, RandomSource random);
protected abstract Rotation getRotation(WorldGenLevel world, BlockPos pos, RandomSource random);
protected abstract Mirror getMirror(WorldGenLevel world, BlockPos pos, RandomSource random);
protected abstract int getYOffset(StructureTemplate structure,
WorldGenLevel world,
BlockPos pos,
RandomSource random);
protected abstract TerrainMerge getTerrainMerge(WorldGenLevel world, BlockPos pos, RandomSource random);
protected abstract void addStructureData(StructurePlaceSettings data);
protected BlockPos getGround(WorldGenLevel world, BlockPos center) {
Holder<Biome> biome = world.getBiome(center);
ResourceLocation id = BiomeAPI.getBiomeID(biome);
if (id.getNamespace().contains("moutain") || id.getNamespace().contains("lake")) {
int y = getAverageY(world, center);
return new BlockPos(center.getX(), y, center.getZ());
} else {
int y = getAverageYWG(world, center);
return new BlockPos(center.getX(), y, center.getZ());
}
}
protected int getAverageY(WorldGenLevel world, BlockPos center) {
int y = getYOnSurface(world, center.getX(), center.getZ());
y += getYOnSurface(world, center.getX() - 2, center.getZ() - 2);
y += getYOnSurface(world, center.getX() + 2, center.getZ() - 2);
y += getYOnSurface(world, center.getX() - 2, center.getZ() + 2);
y += getYOnSurface(world, center.getX() + 2, center.getZ() + 2);
return y / 5;
}
protected int getAverageYWG(WorldGenLevel world, BlockPos center) {
int y = getYOnSurfaceWG(world, center.getX(), center.getZ());
y += getYOnSurfaceWG(world, center.getX() - 2, center.getZ() - 2);
y += getYOnSurfaceWG(world, center.getX() + 2, center.getZ() - 2);
y += getYOnSurfaceWG(world, center.getX() - 2, center.getZ() + 2);
y += getYOnSurfaceWG(world, center.getX() + 2, center.getZ() + 2);
return y / 5;
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel world = context.level();
RandomSource random = context.random();
BlockPos center = context.origin();
center = new BlockPos(((center.getX() >> 4) << 4) | 8, 128, ((center.getZ() >> 4) << 4) | 8);
center = getGround(world, center);
if (!canSpawn(world, center, random)) {
return false;
}
int posY = center.getY() + 1;
StructureTemplate structure = getStructure(world, center, random);
Rotation rotation = getRotation(world, center, random);
Mirror mirror = getMirror(world, center, random);
BlockPos offset = StructureTemplate.transform(
new BlockPos(structure.getSize()),
mirror,
rotation,
BlockPos.ZERO
);
center = center.offset(0, getYOffset(structure, world, center, random) + 0.5, 0);
BoundingBox bounds = makeBox(center);
StructurePlaceSettings placementData = new StructurePlaceSettings()
.setRotation(rotation)
.setMirror(mirror)
.setBoundingBox(bounds);
addStructureData(placementData);
center = center.offset(-offset.getX() * 0.5, 0, -offset.getZ() * 0.5);
structure.placeInWorld(world, center, center, placementData, random, 4);
TerrainMerge merge = getTerrainMerge(world, center, random);
int x1 = center.getX();
int z1 = center.getZ();
int x2 = x1 + offset.getX();
int z2 = z1 + offset.getZ();
if (merge != TerrainMerge.NONE) {
MutableBlockPos mut = new MutableBlockPos();
if (x2 < x1) {
int a = x1;
x1 = x2;
x2 = a;
}
if (z2 < z1) {
int a = z1;
z1 = z2;
z2 = a;
}
int surfMax = posY - 1;
for (int x = x1; x <= x2; x++) {
mut.setX(x);
for (int z = z1; z <= z2; z++) {
mut.setZ(z);
mut.setY(surfMax);
BlockState state = world.getBlockState(mut);
if (!isTerrain(state) && state.isFaceSturdy(world, mut, Direction.DOWN)) {
for (int i = 0; i < 10; i++) {
mut.setY(mut.getY() - 1);
BlockState stateSt = world.getBlockState(mut);
if (!isTerrain(stateSt)) {
if (merge == TerrainMerge.SURFACE) {
boolean isTop = mut.getY() == surfMax && state.getMaterial().isSolidBlocking();
Holder<Biome> b = world.getBiome(mut);
BlockState top = (isTop
? BiomeAPI.findTopMaterial(b)
: BiomeAPI.findUnderMaterial(b)).orElse(defaultBlock);
BlocksHelper.setWithoutUpdate(world, mut, top);
} else {
BlocksHelper.setWithoutUpdate(world, mut, state);
}
} else {
if (isTerrain(state) && state.getMaterial().isSolidBlocking()) {
if (merge == TerrainMerge.SURFACE) {
Holder<Biome> b = world.getBiome(mut);
BlockState bottom = BiomeAPI.findUnderMaterial(b).orElse(defaultBlock);
BlocksHelper.setWithoutUpdate(world, mut, bottom);
} else {
BlocksHelper.setWithoutUpdate(world, mut, state);
}
}
break;
}
}
}
}
}
}
//BlocksHelper.fixBlocks(world, new BlockPos(x1, center.getY(), z1), new BlockPos(x2, center.getY() + offset.getY(), z2));
return true;
}
private boolean isTerrain(BlockState state) {
return state.is(CommonBlockTags.END_STONES) || state.is(CommonBlockTags.NETHER_STONES);
}
protected BoundingBox makeBox(BlockPos pos) {
int sx = ((pos.getX() >> 4) << 4) - 16;
int sz = ((pos.getZ() >> 4) << 4) - 16;
int ex = sx + 47;
int ez = sz + 47;
return BoundingBox.fromCorners(new Vec3i(sx, 0, sz), new Vec3i(ex, 255, ez));
}
protected static StructureTemplate readStructure(ResourceLocation resource) {
String ns = resource.getNamespace();
String nm = resource.getPath();
try {
InputStream inputstream = MinecraftServer.class.getResourceAsStream("/data/" + ns + "/structures/" + nm + ".nbt");
return readStructureFromStream(inputstream);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private static StructureTemplate readStructureFromStream(InputStream stream) throws IOException {
CompoundTag nbttagcompound = NbtIo.readCompressed(stream);
StructureTemplate template = new StructureTemplate();
template.load(nbttagcompound);
return template;
}
public enum TerrainMerge {
NONE, SURFACE, OBJECT;
public static TerrainMerge getFromString(String type) {
if (type.equals("surface")) {
return SURFACE;
} else if (type.equals("object")) {
return OBJECT;
} else {
return NONE;
}
}
}
}

View file

@ -0,0 +1,250 @@
package org.betterx.bclib.api.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;
import net.minecraft.util.RandomSource;
import net.minecraft.util.valueproviders.ClampedNormalInt;
import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
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.feature.configurations.SimpleRandomFeatureConfiguration;
import net.minecraft.world.level.levelgen.placement.*;
import com.mojang.serialization.Codec;
import org.betterx.bclib.api.features.config.ScatterFeatureConfig;
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;
public class ScatterFeature<FC extends ScatterFeatureConfig>
extends Feature<FC> {
public static <T extends ScatterFeatureConfig> BCLFeature createAndRegister(ResourceLocation location,
int minPerChunk,
int maxPerChunk,
T cfg,
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())
.randomHeight4FromFloorCeil()
.modifier(CountPlacement.of(UniformInt.of(2, 5)))
.modifier(RandomOffsetPlacement.of(
ClampedNormalInt.of(0.0f, 2.0f, -6, 6),
ClampedNormalInt.of(0.0f, 0.6f, -2, 2)))
.modifier(BiomeFilter.biome())
.buildAndRegister(configuration);
}
public ScatterFeature(Codec<FC> configCodec) {
super(configCodec);
}
@Override
public boolean place(FeaturePlaceContext<FC> featurePlaceContext) {
final WorldGenLevel level = featurePlaceContext.level();
final BlockPos origin = featurePlaceContext.origin();
final RandomSource random = featurePlaceContext.random();
ScatterFeatureConfig config = featurePlaceContext.config();
Optional<Direction> direction = getTipDirection(level, origin, random, config);
if (direction.isEmpty()) {
return false;
}
BlockPos basePos = origin.relative(direction.get().getOpposite());
int i = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight);
growCenterPillar(level, origin, basePos, direction.get(), i, config, random);
return true;
}
protected void growCenterPillar(LevelAccessor level,
BlockPos origin,
BlockPos basePos,
Direction direction,
int centerHeight,
ScatterFeatureConfig config,
RandomSource random) {
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));
final int tryCount = config.spreadCount.sample(random);
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.findSurroundingSurface(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;
myHeight = (int) Math.min(Math.max(
config.minHeight,
config.minHeight + sizeFactor * (myHeight - config.minHeight)
), config.maxHeight);
buildPillarWithBase(level,
POS,
POS.relative(direction.getOpposite()),
direction,
myHeight,
config,
random);
}
}
}
}
private void buildPillarWithBase(LevelAccessor level,
BlockPos origin,
BlockPos basePos,
Direction direction,
int height,
ScatterFeatureConfig config,
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, random);
}
}
private void buildPillar(LevelAccessor level,
BlockPos origin,
Direction direction,
int height,
ScatterFeatureConfig config,
RandomSource random) {
final BlockPos.MutableBlockPos POS = origin.mutable();
buildBaseToTipColumn(height, (blockState) -> {
BlocksHelper.setWithoutUpdate(level, POS, blockState);
POS.move(direction);
}, config, random);
}
protected void buildBaseToTipColumn(int totalHeight,
Consumer<BlockState> 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());
}
}
private Optional<Direction> getTipDirection(LevelAccessor levelAccessor,
BlockPos blockPos,
RandomSource randomSource,
ScatterFeatureConfig config) {
boolean onCeil = config.floorChance < 1 && config.isValidBase(levelAccessor.getBlockState(blockPos.above()));
boolean onFloor = config.floorChance > 0 && config.isValidBase(levelAccessor.getBlockState(blockPos.below()));
if (onCeil && onFloor) {
return Optional.of(config.isFloor(randomSource) ? Direction.DOWN : Direction.UP);
}
if (onCeil) {
return Optional.of(Direction.DOWN);
}
if (onFloor) {
return Optional.of(Direction.UP);
}
return Optional.empty();
}
private void createPatchOfBaseBlocks(LevelAccessor levelAccessor,
RandomSource randomSource,
BlockPos blockPos,
ScatterFeatureConfig config) {
if (config.baseState.isPresent() && config.baseReplaceChance > 0 && randomSource.nextFloat() < config.baseReplaceChance) {
final BlockState baseState = config.baseState.get();
BlockPos pos;
for (Direction direction : Direction.Plane.HORIZONTAL) {
if (randomSource.nextFloat() > config.chanceOfDirectionalSpread) continue;
pos = blockPos.relative(direction);
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
if (randomSource.nextFloat() > config.chanceOfSpreadRadius2) continue;
pos = pos.relative(Direction.getRandom(randomSource));
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
if (randomSource.nextFloat() > config.chanceOfSpreadRadius3) continue;
pos = pos.relative(Direction.getRandom(randomSource));
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
}
placeBaseBlockIfPossible(levelAccessor, blockPos, baseState);
}
}
protected void placeBaseBlockIfPossible(LevelAccessor levelAccessor,
BlockPos blockPos,
BlockState baseState) {
BlockState blockState = levelAccessor.getBlockState(blockPos);
if (blockState.is(CommonBlockTags.TERRAIN)) {
levelAccessor.setBlock(blockPos, baseState, 2);
}
}
}

View file

@ -0,0 +1,48 @@
package org.betterx.bclib.api.features;
import net.minecraft.core.BlockPos;
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 net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import com.mojang.serialization.Codec;
import org.betterx.bclib.util.BlocksHelper;
import java.util.Optional;
public abstract class SurfaceFeature<T extends FeatureConfiguration> extends Feature<T> {
public static abstract class DefaultConfiguration extends SurfaceFeature<NoneFeatureConfiguration> {
protected DefaultConfiguration() {
super(NoneFeatureConfiguration.CODEC);
}
}
protected SurfaceFeature(Codec<T> codec) {
super(codec);
}
protected abstract boolean isValidSurface(BlockState state);
protected int minHeight(FeaturePlaceContext<T> ctx) {
return ctx.chunkGenerator().getSeaLevel();
}
@Override
public boolean place(FeaturePlaceContext<T> ctx) {
Optional<BlockPos> pos = BlocksHelper.findSurfaceBelow(ctx.level(),
ctx.origin(),
minHeight(ctx),
this::isValidSurface);
if (pos.isPresent()) {
generate(pos.get(), ctx);
return true;
}
return false;
}
protected abstract void generate(BlockPos centerPos, FeaturePlaceContext<T> ctx);
}

View file

@ -0,0 +1,84 @@
package org.betterx.bclib.api.features;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.GenerationStep;
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.BCLib;
import org.betterx.bclib.api.features.config.TemplateFeatureConfig;
import org.betterx.bclib.world.structures.StructureNBT;
import org.betterx.bclib.world.structures.StructureWorldNBT;
public class TemplateFeature<FC extends TemplateFeatureConfig> extends Feature<FC> {
public static final Feature<TemplateFeatureConfig> INSTANCE = BCLFeature.register(BCLib.makeID("template"),
new TemplateFeature(
TemplateFeatureConfig.CODEC));
public static <T extends TemplateFeatureConfig> BCLFeature createAndRegisterRare(ResourceLocation location,
TemplateFeatureConfig configuration,
int onceEveryChunk) {
return BCLFeatureBuilder
.start(location, INSTANCE)
.decoration(GenerationStep.Decoration.SURFACE_STRUCTURES)
.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()
.randomHeight10FromFloorCeil()
.findSolidFloor(12) //cast downward ray to find solid surface
.isEmptyAbove4()
.onlyInBiome()
.buildAndRegister(configuration);
}
public TemplateFeature(Codec<FC> codec) {
super(codec);
}
protected StructureWorldNBT randomStructure(TemplateFeatureConfig cfg, RandomSource random) {
if (cfg.structures.size() > 1) {
final float chanceSum = cfg.structures.parallelStream().map(c -> c.chance).reduce(0.0f, (p, c) -> p + c);
float rnd = random.nextFloat() * chanceSum;
for (StructureWorldNBT c : cfg.structures) {
rnd -= c.chance;
if (rnd <= 0) return c;
}
} else {
return cfg.structures.get(0);
}
return null;
}
@Override
public boolean place(FeaturePlaceContext<FC> ctx) {
StructureWorldNBT structure = randomStructure(ctx.config(), ctx.random());
return structure.generateIfPlaceable(ctx.level(),
ctx.origin(),
StructureNBT.getRandomRotation(ctx.random()),
StructureNBT.getRandomMirror(ctx.random())
);
}
}

View file

@ -0,0 +1,59 @@
package org.betterx.bclib.api.features.config;
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<BlockPlaceFeatureConfig> CODEC = SimpleWeightedRandomList
.wrappedCodec(BlockState.CODEC)
.comapFlatMap(BlockPlaceFeatureConfig::create, cfg -> cfg.weightedList)
.fieldOf("entries").codec();
private final SimpleWeightedRandomList<BlockState> weightedList;
private static DataResult<BlockPlaceFeatureConfig> create(SimpleWeightedRandomList<BlockState> simpleWeightedRandomList) {
if (simpleWeightedRandomList.isEmpty()) {
return DataResult.error("BlockPlaceFeatureConfig with no states");
}
return DataResult.success(new BlockPlaceFeatureConfig(simpleWeightedRandomList));
}
private static SimpleWeightedRandomList<BlockState> convert(List<BlockState> states) {
var builder = SimpleWeightedRandomList.<BlockState>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
.<BlockState>builder()
.add(state, 1)
.build());
}
public BlockPlaceFeatureConfig(List<BlockState> states) {
this(convert(states));
}
public BlockPlaceFeatureConfig(SimpleWeightedRandomList<BlockState> blocks) {
this.weightedList = blocks;
}
public Optional<BlockState> getRandomBlock(RandomSource random) {
return this.weightedList.getRandomValue(random);
}
}

View file

@ -0,0 +1,367 @@
package org.betterx.bclib.api.features.config;
import net.minecraft.util.RandomSource;
import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.util.valueproviders.UniformInt;
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.Function15;
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<T extends ScatterFeatureConfig> extends Function15<BlockState, BlockState, BlockState, Optional<BlockState>, Float, Float, Float, Float, Integer, Integer, Float, Float, Float, Boolean, IntProvider, T> {
}
public final BlockState clusterBlock;
public final BlockState tipBlock;
public final BlockState bottomBlock;
public final Optional<BlockState> baseState;
public final float baseReplaceChance;
public final float chanceOfDirectionalSpread;
public final float chanceOfSpreadRadius2;
public final float chanceOfSpreadRadius3;
public final int minHeight;
public final int maxHeight;
public final float maxSpread;
public final float sizeVariation;
public final float floorChance;
public final IntProvider spreadCount;
public final boolean growWhileFree;
public ScatterFeatureConfig(BlockState clusterBlock,
BlockState tipBlock,
BlockState bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount) {
this.clusterBlock = clusterBlock;
this.tipBlock = tipBlock == null ? clusterBlock : tipBlock;
this.bottomBlock = bottomBlock == null ? clusterBlock : bottomBlock;
this.baseState = baseState;
this.baseReplaceChance = baseReplaceChance;
this.chanceOfDirectionalSpread = chanceOfDirectionalSpread;
this.chanceOfSpreadRadius2 = chanceOfSpreadRadius2;
this.chanceOfSpreadRadius3 = chanceOfSpreadRadius3;
this.minHeight = minHeight;
this.maxHeight = maxHeight;
this.maxSpread = maxSpread;
this.sizeVariation = sizeVariation;
this.floorChance = floorChance;
this.growWhileFree = growWhileFree;
this.spreadCount = spreadCount;
}
public boolean isFloor(RandomSource random) {
return random.nextFloat() < floorChance;
}
public abstract boolean isValidBase(BlockState state);
public abstract BlockState createBlock(int height, int maxHeight, RandomSource random);
public static <T extends ScatterFeatureConfig> Codec<T> buildCodec(Instancer<T> 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),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("baseReplaceChance")
.orElse(1.0F)
.forGetter((T cfg) -> cfg.baseReplaceChance),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_directional_spread")
.orElse(0.7F)
.forGetter((T cfg) -> cfg.chanceOfDirectionalSpread),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_spread_radius2")
.orElse(0.5F)
.forGetter((T cfg) -> cfg.chanceOfSpreadRadius2),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_spread_radius3")
.orElse(0.5F)
.forGetter((T cfg) -> cfg.chanceOfSpreadRadius3),
Codec
.intRange(1, 20)
.fieldOf("min_height")
.orElse(2)
.forGetter((T cfg) -> cfg.minHeight),
Codec
.intRange(1, 20)
.fieldOf("max_height")
.orElse(7)
.forGetter((T cfg) -> cfg.maxHeight),
Codec
.floatRange(0, 10)
.fieldOf("max_spread")
.orElse(2f)
.forGetter((T cfg) -> cfg.maxSpread),
Codec
.floatRange(0, 1)
.fieldOf("size_variation")
.orElse(0.7f)
.forGetter((T cfg) -> cfg.sizeVariation),
Codec
.floatRange(0, 1)
.fieldOf("floor_chance")
.orElse(0.5f)
.forGetter((T cfg) -> cfg.floorChance),
Codec
.BOOL
.fieldOf("grow_while_empty")
.orElse(false)
.forGetter((T cfg) -> cfg.growWhileFree),
IntProvider.codec(0, 64)
.fieldOf("length")
.orElse(UniformInt.of(0, 3))
.forGetter(cfg -> cfg.spreadCount)
)
.apply(instance, instancer)
);
}
public static class Builder<T extends ScatterFeatureConfig> {
private BlockState clusterBlock;
private BlockState tipBlock;
private BlockState bottomBlock;
private Optional<BlockState> 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;
public IntProvider spreadCount = ConstantInt.of(0);
private final Instancer<T> instancer;
public Builder(Instancer<T> instancer) {
this.instancer = instancer;
}
public static <T extends ScatterFeatureConfig> Builder<T> start(Instancer<T> instancer) {
return new Builder<>(instancer);
}
public Builder<T> block(Block b) {
return block(b.defaultBlockState());
}
public Builder<T> singleBlock(Block b) {
return block(b.defaultBlockState()).heightRange(1, 1).spread(0, 0);
}
public Builder<T> block(BlockState s) {
this.clusterBlock = s;
if (tipBlock == null) tipBlock = s;
if (bottomBlock == null) bottomBlock = s;
return this;
}
public Builder<T> tipBlock(BlockState s) {
tipBlock = s;
return this;
}
public Builder<T> bottomBlock(BlockState s) {
bottomBlock = s;
return this;
}
public Builder<T> heightRange(int min, int max) {
minHeight = min;
maxHeight = max;
return this;
}
public Builder<T> growWhileFree() {
growWhileFree = true;
return this;
}
public Builder<T> minHeight(int h) {
minHeight = h;
return this;
}
public Builder<T> maxHeight(int h) {
maxHeight = h;
return this;
}
public Builder<T> generateBaseBlock(BlockState baseState) {
return generateBaseBlock(baseState, 1, 0, 0, 0);
}
public Builder<T> generateBaseBlock(BlockState baseState, float baseReplaceChance) {
return generateBaseBlock(baseState, baseReplaceChance, 0, 0, 0);
}
public Builder<T> generateBaseBlock(BlockState baseState,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3) {
return generateBaseBlock(baseState,
1,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3);
}
public Builder<T> 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<T> spread(float maxSpread, float sizeVariation) {
return spread(maxSpread, sizeVariation, ConstantInt.of((int) Math.min(16, 4 * maxSpread * maxSpread)));
}
public Builder<T> spread(float maxSpread, float sizeVariation, IntProvider spreadCount) {
this.spreadCount = spreadCount; //
this.maxSpread = maxSpread;
this.sizeVariation = sizeVariation;
return this;
}
public Builder<T> floorChance(float chance) {
this.floorChance = chance;
return this;
}
public Builder<T> onFloor() {
this.floorChance = 1;
return this;
}
public Builder<T> 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,
this.spreadCount
);
}
}
public static class OnSolid extends ScatterFeatureConfig {
public static final Codec<OnSolid> CODEC = buildCodec(OnSolid::new);
public OnSolid(BlockState clusterBlock,
BlockState tipBlock,
BlockState bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount) {
super(clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount);
}
public static Builder<OnSolid> 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;
}
}
}

View file

@ -0,0 +1,40 @@
package org.betterx.bclib.api.features.config;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import org.betterx.bclib.world.structures.StructurePlacementType;
import org.betterx.bclib.world.structures.StructureWorldNBT;
import java.util.List;
public class TemplateFeatureConfig implements FeatureConfiguration {
public static final Codec<TemplateFeatureConfig> CODEC = RecordCodecBuilder.create((instance) -> instance
.group(
ExtraCodecs.nonEmptyList(StructureWorldNBT.CODEC.listOf())
.fieldOf("structures")
.forGetter((TemplateFeatureConfig cfg) -> cfg.structures)
)
.apply(instance, TemplateFeatureConfig::new)
);
public final List<StructureWorldNBT> structures;
public static StructureWorldNBT cfg(ResourceLocation location,
int offsetY,
StructurePlacementType type,
float chance) {
return StructureWorldNBT.create(location, offsetY, type, chance);
}
public TemplateFeatureConfig(ResourceLocation location, int offsetY, StructurePlacementType type) {
this(List.of(cfg(location, offsetY, type, 1.0f)));
}
public TemplateFeatureConfig(List<StructureWorldNBT> structures) {
this.structures = structures;
}
}

View file

@ -0,0 +1,56 @@
package org.betterx.bclib.api.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.stream.Stream;
public class Extend extends PlacementModifier {
public static final Codec<Extend> 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<BlockPos> getPositions(PlacementContext placementContext,
RandomSource random,
BlockPos blockPos) {
var builder = Stream.<BlockPos>builder();
final int count = length.sample(random);
builder.add(blockPos);
for (int y = 1; y < count + 1; y++) {
builder.add(blockPos.relative(direction, y));
}
return builder.build();
}
@Override
public PlacementModifierType<?> type() {
return PlacementModifiers.EXTEND;
}
}

View file

@ -0,0 +1,74 @@
package org.betterx.bclib.api.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, 6);
protected static final FindSolidInDirection UP = new FindSolidInDirection(Direction.UP, 6);
public static final Codec<FindSolidInDirection> 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<BlockPos> getPositions(PlacementContext placementContext,
RandomSource randomSource,
BlockPos blockPos) {
BlockPos.MutableBlockPos POS = blockPos.mutable();
if (BlocksHelper.findSurroundingSurface(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;
}
}

View file

@ -0,0 +1,43 @@
package org.betterx.bclib.api.features.placement;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
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;
public class Is extends PlacementFilter {
public static final Codec<Is> CODEC = RecordCodecBuilder.create((instance) -> instance
.group(
BlockPredicate.CODEC
.fieldOf("predicate")
.forGetter(cfg -> cfg.predicate)
)
.apply(instance, Is::new));
private final BlockPredicate predicate;
public Is(BlockPredicate predicate) {
this.predicate = predicate;
}
public static Is simple(BlockPredicate predicate) {
return new Is(predicate);
}
@Override
protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) {
WorldGenLevel level = ctx.getLevel();
return predicate.test(level, pos);
}
@Override
public PlacementModifierType<Is> type() {
return PlacementModifiers.IS;
}
}

View file

@ -0,0 +1,48 @@
package org.betterx.bclib.api.features.placement;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
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;
public class IsBasin extends PlacementFilter {
public static final Codec<IsBasin> CODEC = RecordCodecBuilder.create((instance) -> instance
.group(
BlockPredicate.CODEC
.fieldOf("predicate")
.forGetter(cfg -> cfg.predicate)
)
.apply(instance, IsBasin::new));
private final BlockPredicate predicate;
public IsBasin(BlockPredicate predicate) {
this.predicate = predicate;
}
public static IsBasin simple(BlockPredicate predicate) {
return new IsBasin(predicate);
}
@Override
protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) {
WorldGenLevel level = ctx.getLevel();
return predicate.test(level, pos.below())
&& predicate.test(level, pos.west())
&& predicate.test(level, pos.east())
&& predicate.test(level, pos.north())
&& predicate.test(level, pos.south());
}
@Override
public PlacementModifierType<?> type() {
return PlacementModifiers.IS_BASIN;
}
}

View file

@ -0,0 +1,57 @@
package org.betterx.bclib.api.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);
private static final IsEmptyAboveSampledFilter DEFAULT1 = new IsEmptyAboveSampledFilter(1, 1);
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 static PlacementFilter emptyAbove() {
return DEFAULT1;
}
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))
&& (distance1 == distance2 || 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,64 @@
package org.betterx.bclib.api.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, 2);
private static MinEmptyFilter UP = new MinEmptyFilter(Direction.UP, 2);
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.distance)
)
.apply(instance, MinEmptyFilter::new));
private final Direction direction;
private final int distance;
protected MinEmptyFilter(Direction direction, int distance) {
this.direction = direction;
this.distance = distance;
}
public static PlacementModifier down() {
return DOWN;
}
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) {
return BlocksHelper.isFreeSpace(
ctx.getLevel(),
pos.relative(direction),
direction,
distance - 1,
state -> state.getMaterial().isReplaceable()
);
}
@Override
public PlacementModifierType<?> type() {
return PlacementModifiers.MIN_EMPTY_FILTER;
}
}

View file

@ -0,0 +1,54 @@
package org.betterx.bclib.api.features.placement;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
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.google.common.collect.Maps;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Map;
import java.util.stream.Stream;
public class Offset extends PlacementModifier {
private static Map<Direction, Offset> DIRECTIONS = Maps.newHashMap();
public static final Codec<Offset> 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;
}
public static Offset inDirection(Direction dir) {
return DIRECTIONS.get(dir);
}
@Override
public Stream<BlockPos> getPositions(PlacementContext placementContext,
RandomSource randomSource,
BlockPos blockPos) {
return Stream.of(blockPos.offset(offset));
}
@Override
public PlacementModifierType<?> type() {
return PlacementModifiers.OFFSET;
}
static {
for (Direction d : Direction.values())
DIRECTIONS.put(d, new Offset(d.getNormal()));
}
}

View file

@ -0,0 +1,74 @@
package org.betterx.bclib.api.features.placement;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
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 java.util.stream.Stream;
public class OnEveryLayer
extends PlacementModifier {
private static OnEveryLayer INSTANCE = new OnEveryLayer();
public static final Codec<OnEveryLayer> CODEC = Codec.unit(() -> INSTANCE);
private OnEveryLayer() {
}
public static OnEveryLayer simple() {
return INSTANCE;
}
@Override
public Stream<BlockPos> getPositions(PlacementContext ctx,
RandomSource random,
BlockPos pos) {
Stream.Builder<BlockPos> builder = Stream.builder();
final int z = pos.getZ();
final int x = pos.getX();
int y = ctx.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z);
int layerY;
do {
layerY = OnEveryLayer.findOnGroundYPosition(ctx, x, y, z);
if (layerY != Integer.MAX_VALUE) {
builder.add(new BlockPos(x, layerY, z));
y = layerY - 1;
}
} while (layerY != Integer.MAX_VALUE);
return builder.build();
}
@Override
public PlacementModifierType<OnEveryLayer> type() {
return PlacementModifiers.ON_EVERY_LAYER;
}
private static int findOnGroundYPosition(PlacementContext ctx, int x, int startY, int z) {
BlockPos.MutableBlockPos mPos = new BlockPos.MutableBlockPos(x, startY, z);
BlockState nowState = ctx.getBlockState(mPos);
for (int y = startY; y >= ctx.getMinBuildHeight() + 1; --y) {
mPos.setY(y - 1);
BlockState belowState = ctx.getBlockState(mPos);
if (!OnEveryLayer.isEmpty(belowState) && OnEveryLayer.isEmpty(nowState) && !belowState.is(Blocks.BEDROCK)) {
return mPos.getY() + 1;
}
nowState = belowState;
}
return Integer.MAX_VALUE;
}
private static boolean isEmpty(BlockState blockState) {
return blockState.isAir() || blockState.is(Blocks.WATER) || blockState.is(Blocks.LAVA);
}
}

View file

@ -0,0 +1,62 @@
package org.betterx.bclib.api.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);
public static final PlacementModifierType<FindSolidInDirection> SOLID_IN_DIR = register(
"solid_in_dir",
FindSolidInDirection.CODEC);
public static final PlacementModifierType<Stencil> STENCIL = register(
"stencil",
Stencil.CODEC);
public static final PlacementModifierType<IsBasin> IS_BASIN = register(
"is_basin",
IsBasin.CODEC);
public static final PlacementModifierType<Is> IS = register(
"is",
Is.CODEC);
public static final PlacementModifierType<Offset> OFFSET = register(
"offset",
Offset.CODEC);
public static final PlacementModifierType<Extend> EXTEND = register(
"extend",
Extend.CODEC);
public static final PlacementModifierType<OnEveryLayer> ON_EVERY_LAYER = register(
"on_every_layer",
OnEveryLayer.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

@ -0,0 +1,344 @@
package org.betterx.bclib.api.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<Stencil> CODEC;
private static final Boolean[] BN_STENCIL;
private final List<Boolean> stencil;
private static final Stencil DEFAULT;
private static final Stencil DEFAULT4;
private final int selectOneIn;
private static List<Boolean> convert(Boolean[] s) {
return Arrays.stream(s).toList();
}
public Stencil(Boolean[] stencil, int selectOneIn) {
this(convert(stencil), selectOneIn);
}
public Stencil(List<Boolean> stencil, int selectOneIn) {
this.stencil = stencil;
this.selectOneIn = selectOneIn;
}
public static Stencil all() {
return DEFAULT;
}
public static Stencil oneIn4() {
return DEFAULT4;
}
@Override
public Stream<BlockPos> getPositions(PlacementContext placementContext,
RandomSource randomSource,
BlockPos blockPos) {
List<BlockPos> 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, 1);
DEFAULT4 = new Stencil(BN_STENCIL, 4);
CODEC = RecordCodecBuilder.create((instance) -> instance
.group(
ExtraCodecs.nonEmptyList(Codec.BOOL.listOf())
.fieldOf("structures")
.orElse(convert(BN_STENCIL))
.forGetter((Stencil a) -> a.stencil),
Codec.INT
.fieldOf("one_in")
.orElse(1)
.forGetter((Stencil a) -> a.selectOneIn)
)
.apply(instance, Stencil::new)
);
}
}