Adjust tree spawn rate and add a sweet apple orchard biome

This commit is contained in:
Sollace 2024-02-10 18:16:15 +00:00
parent 9599d0044b
commit ded8499e9f
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
14 changed files with 592 additions and 50 deletions

View file

@ -66,7 +66,7 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return true;
}
protected BlockState getPlacedFruitState(Random random) {
public BlockState getPlacedFruitState(Random random) {
return fruit.get().getDefaultState();
}
@ -107,7 +107,7 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
BlockState fruitState = world.getBlockState(fruitPosition);
if (stage == Stage.WITHERING && fruitState.isOf(fruit.get())) {
if (stage == Stage.WITHERING && fruitState.isOf(fruit.get()) && world.isPlayerInRange(pos.getX(), pos.getY(), pos.getZ(), 25)) {
if (world.random.nextInt(2) == 0) {
Block.dropStack(world, fruitPosition, rottenFruitSupplier.get());
} else {
@ -141,7 +141,7 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return TintedBlock.blend(foliageColor, overlay);
}
private boolean isPositionValidForFruit(BlockState state, BlockPos pos) {
public boolean isPositionValidForFruit(BlockState state, BlockPos pos) {
return state.getRenderingSeed(pos) % 3 == 1;
}

View file

@ -20,7 +20,7 @@ public class GoldenOakLeavesBlock extends FruitBearingBlock {
}
@Override
protected BlockState getPlacedFruitState(Random random) {
public BlockState getPlacedFruitState(Random random) {
return super.getPlacedFruitState(random).with(EnchantedFruitBlock.ENCHANTED, random.nextInt(1000) == 0);
}
}

View file

@ -0,0 +1,23 @@
package com.minelittlepony.unicopia.mixin;
import java.util.function.Consumer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import com.minelittlepony.unicopia.server.world.gen.BiomeSelectionInjector;
import com.mojang.datafixers.util.Pair;
import net.minecraft.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.util.MultiNoiseUtil;
import net.minecraft.world.biome.source.util.VanillaBiomeParameters;
@Mixin(VanillaBiomeParameters.class)
abstract class MixinVanillaBiomeParameters {
@ModifyVariable(method = "writeOverworldBiomeParameters", at = @At("HEAD"))
private Consumer<Pair<MultiNoiseUtil.NoiseHypercube, RegistryKey<Biome>>> onWriteOverworldBiomeParameters(Consumer<Pair<MultiNoiseUtil.NoiseHypercube, RegistryKey<Biome>>> parametersCollector) {
return new BiomeSelectionInjector(parametersCollector);
}
}

View file

@ -4,6 +4,7 @@ import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.minelittlepony.unicopia.block.UBlocks;
@ -17,10 +18,11 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random;
import net.minecraft.registry.*;
import net.minecraft.registry.tag.BiomeTags;
import net.minecraft.world.gen.GenerationStep;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.gen.feature.*;
import net.minecraft.world.gen.feature.size.TwoLayersFeatureSize;
import net.minecraft.world.gen.foliage.FoliagePlacer;
import net.minecraft.world.gen.GenerationStep;
import net.minecraft.world.gen.placementmodifier.PlacementModifier;
import net.minecraft.world.gen.stateprovider.BlockStateProvider;
import net.minecraft.world.gen.trunk.TrunkPlacer;
@ -29,9 +31,8 @@ public record Tree (
Identifier id,
TreeFeatureConfig.Builder config,
RegistryKey<ConfiguredFeature<?, ?>> configuredFeatureId,
Optional<RegistryKey<PlacedFeature>> placedFeatureId,
Optional<Block> sapling,
Optional<PlacementModifier> placement
Set<Placement> placements,
Optional<Block> sapling
) {
public static final List<Tree> REGISTRY = new ArrayList<>();
@ -44,20 +45,22 @@ public record Tree (
});
registries.getOptional(RegistryKeys.PLACED_FEATURE).ifPresent(registry -> {
var reg = registries.asDynamicRegistryManager().createRegistryLookup().getOrThrow(RegistryKeys.CONFIGURED_FEATURE);
REGISTRY.stream().filter(tree -> tree.placedFeatureId().isPresent()).forEach(tree -> {
var placedFeature = new PlacedFeature(reg.getOrThrow(tree.configuredFeatureId()),
VegetationPlacedFeatures.treeModifiersWithWouldSurvive(tree.placement().orElseThrow(), tree.sapling().orElse(Blocks.OAK_SAPLING))
);
Registry.register(registry, tree.id, placedFeature);
REGISTRY.stream().forEach(tree -> {
tree.placements().forEach(placement -> {
Registry.register(registry, placement.id(), new PlacedFeature(reg.getOrThrow(tree.configuredFeatureId()),
VegetationPlacedFeatures.treeModifiersWithWouldSurvive(placement.count(), tree.sapling().orElse(Blocks.OAK_SAPLING))
));
});
});
});
});
}
public static class Builder {
public static final Predicate<BiomeSelectionContext> IS_FOREST = BiomeSelectors.foundInOverworld().and(BiomeSelectors.tag(BiomeTags.IS_FOREST));
public static final Predicate<BiomeSelectionContext> IS_OAK_FOREST = IS_FOREST
.and(BiomeSelectors.excludeByKey(BiomeKeys.BIRCH_FOREST, BiomeKeys.OLD_GROWTH_BIRCH_FOREST, BiomeKeys.DARK_FOREST))
.and(BiomeSelectors.tag(BiomeTags.IS_TAIGA).negate());
public static Builder create(Identifier id, TrunkPlacer trunkPlacer, FoliagePlacer foliagePlacer) {
return new Builder(id, trunkPlacer, foliagePlacer);
@ -73,8 +76,7 @@ public record Tree (
private final Identifier id;
private Optional<Predicate<BiomeSelectionContext>> selector = Optional.empty();
private Optional<PlacementModifier> countModifier = Optional.empty();
private Map<Identifier, Placement> placements = new HashMap<>();
private Function<TreeFeatureConfig.Builder, TreeFeatureConfig.Builder> configParameters = Function.identity();
private Optional<TwoLayersFeatureSize> size = Optional.empty();
@ -104,13 +106,18 @@ public record Tree (
return this;
}
public Builder count(int count, float extraChance, int extraCount) {
countModifier = Optional.of(PlacedFeatures.createCountExtraModifier(count, extraChance, extraCount));
return this;
public Builder placement(int count, float extraChance, int extraCount, Predicate<BiomeSelectionContext> selector) {
return placement("", count, extraChance, extraCount, selector);
}
public Builder biomes(Predicate<BiomeSelectionContext> selector) {
this.selector = Optional.of(selector);
public Builder placement(String suffex, int count, float extraChance, int extraCount, Predicate<BiomeSelectionContext> selector) {
Identifier id = this.id.withSuffixedPath("/placed" + (suffex.isEmpty() ? "" : "/") + suffex);
placements.put(id, new Placement(
id,
PlacedFeatures.createCountExtraModifier(count, extraChance, extraCount),
RegistryKey.of(RegistryKeys.PLACED_FEATURE, id),
selector
));
return this;
}
@ -119,7 +126,7 @@ public record Tree (
return this;
}
public Builder farmingCondition(int yLevel, int sizeBelowY, int sizeAboveY) {
public Builder dimensions(int yLevel, int sizeBelowY, int sizeAboveY) {
this.size = Optional.of(new TwoLayersFeatureSize(yLevel, Math.max(0, sizeBelowY), Math.max(0, sizeAboveY)));
return this;
}
@ -132,23 +139,28 @@ public record Tree (
BlockStateProvider.of(leavesType),
foliagePlacer,
size.get()
)), configuredFeatureId, selector.map(selector -> {
RegistryKey<PlacedFeature> i = RegistryKey.of(RegistryKeys.PLACED_FEATURE, id);
BiomeModifications.addFeature(selector, GenerationStep.Feature.VEGETAL_DECORATION, i);
return i;
}), saplingId.map(id -> UBlocks.register(id, saplingConstructor.apply(new SaplingGenerator() {
)), configuredFeatureId, placements.values().stream()
.collect(Collectors.toUnmodifiableSet()),
saplingId.map(id -> UBlocks.register(id, saplingConstructor.apply(new SaplingGenerator() {
@Override
protected RegistryKey<ConfiguredFeature<?, ?>> getTreeFeature(Random rng, boolean flowersNearby) {
return configuredFeatureId;
}
}, FabricBlockSettings.copy(Blocks.OAK_SAPLING)), ItemGroups.NATURAL)), countModifier);
}, FabricBlockSettings.copy(Blocks.OAK_SAPLING)), ItemGroups.NATURAL)));
if (REGISTRY.isEmpty()) {
bootstrap();
}
REGISTRY.add(tree);
tree.placements().forEach(placement -> {
BiomeModifications.addFeature(placement.selector(), GenerationStep.Feature.VEGETAL_DECORATION, placement.feature());
});
return tree;
}
}
public record Placement(Identifier id, PlacementModifier count, RegistryKey<PlacedFeature> feature, Predicate<BiomeSelectionContext> selector) {
}
}

View file

@ -3,7 +3,9 @@ package com.minelittlepony.unicopia.server.world;
import com.google.common.collect.ImmutableList;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.server.world.gen.FruitBlobFoliagePlacer;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
import net.minecraft.block.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.intprovider.ConstantIntProvider;
@ -39,19 +41,20 @@ public interface UTreeGen {
.log(UBlocks.ZAP_LOG)
.leaves(UBlocks.ZAP_LEAVES)
.sapling(Unicopia.id("zapling"))
.biomes(Tree.Builder.IS_FOREST)
.count(0, 0.01F, 1)
.farmingCondition(6, 0, 8)
.placement(0, 0.01F, 1, Tree.Builder.IS_FOREST)
.dimensions(6, 0, 8)
.build();
Tree GREEN_APPLE_TREE = createAppleTree("green_apple", UBlocks.GREEN_APPLE_LEAVES, 2);
Tree SWEET_APPLE_TREE = createAppleTree("sweet_apple", UBlocks.SWEET_APPLE_LEAVES, 3);
Tree SOUR_APPLE_TREE = createAppleTree("sour_apple", UBlocks.SOUR_APPLE_LEAVES, 5);
Tree GREEN_APPLE_TREE = createAppleTree("green_apple", UBlocks.GREEN_APPLE_LEAVES, 2, 0.2F).build();
Tree SWEET_APPLE_TREE = createAppleTree("sweet_apple", UBlocks.SWEET_APPLE_LEAVES, 3, 0.1F)
.placement("orchard", 6, 0.1F, 3, BiomeSelectors.includeByKey(UWorldGen.SWEET_APPLE_ORCHARD))
.build();
Tree SOUR_APPLE_TREE = createAppleTree("sour_apple", UBlocks.SOUR_APPLE_LEAVES, 5, 0.2F).build();
Tree GOLDEN_APPLE_TREE = Tree.Builder.create(Unicopia.id("golden_oak_tree"),
new StraightTrunkPlacer(6, 1, 3),
new BlobFoliagePlacer(ConstantIntProvider.create(3), ConstantIntProvider.create(0), 3)
)
.configure(TreeFeatureConfig.Builder::forceDirt)
.farmingCondition(1, 3, 5)
.dimensions(1, 3, 5)
.log(UBlocks.GOLDEN_OAK_LOG)
.leaves(UBlocks.GOLDEN_OAK_LEAVES)
.sapling(Unicopia.id("golden_oak_sapling"))
@ -60,7 +63,7 @@ public interface UTreeGen {
new StraightTrunkPlacer(4, 5, 3),
new FernFoliagePlacer(ConstantIntProvider.create(4), ConstantIntProvider.create(0))
)
.farmingCondition(6, 0, 8)
.dimensions(6, 0, 8)
.log(UBlocks.PALM_LOG)
.leaves(UBlocks.PALM_LEAVES)
.sapling(Unicopia.id("palm_sapling")).sapling((generator, settings) -> {
@ -72,36 +75,33 @@ public interface UTreeGen {
};
})
.configure(builder -> builder.dirtProvider(BlockStateProvider.of(Blocks.SAND)))
.biomes(selector -> selector.hasTag(BiomeTags.IS_BEACH) || selector.hasTag(BiomeTags.IS_JUNGLE))
.count(2, 0.01F, 1)
.placement(2, 0.01F, 1, selector -> selector.hasTag(BiomeTags.IS_BEACH) || selector.hasTag(BiomeTags.IS_JUNGLE))
.build();
Tree MANGO_TREE = Tree.Builder.create(Unicopia.id("mango_tree"),
new StraightTrunkPlacer(4, 7, 3),
new BlobFoliagePlacer(ConstantIntProvider.create(3), ConstantIntProvider.create(0), 3)
)
.farmingCondition(9, 0, 4)
.dimensions(9, 0, 4)
.log(Blocks.JUNGLE_LOG)
.leaves(UBlocks.MANGO_LEAVES)
.biomes(selector -> selector.hasTag(BiomeTags.IS_JUNGLE) && selector.getBiomeKey() != BiomeKeys.SPARSE_JUNGLE)
.sapling(Unicopia.id("mango_sapling"))
.count(1, 1, 2)
.placement(1, 1, 2, selector -> selector.hasTag(BiomeTags.IS_JUNGLE) && selector.getBiomeKey() != BiomeKeys.SPARSE_JUNGLE)
.configure(builder -> builder.decorators(ImmutableList.of(TrunkVineTreeDecorator.INSTANCE, new LeavesVineTreeDecorator(0.25f))))
.build();
static Tree createAppleTree(String name, Block leaves, int preferredDensity) {
static Tree.Builder createAppleTree(String name, Block leaves, int preferredDensity, float spawnRate) {
return Tree.Builder.create(Unicopia.id(name + "_tree"),
new StraightTrunkPlacer(4, 3, 2),
new BlobFoliagePlacer(ConstantIntProvider.create(3), ConstantIntProvider.create(0), 3)
new FruitBlobFoliagePlacer(ConstantIntProvider.create(3), ConstantIntProvider.create(0), 3)
)
.configure(TreeFeatureConfig.Builder::forceDirt)
.biomes(selector -> selector.hasTag(BiomeTags.IS_FOREST))
.count(2, 0.01F, 1)
.farmingCondition(1, preferredDensity - 2, preferredDensity)
.placement(0, spawnRate, 4, Tree.Builder.IS_OAK_FOREST)
.dimensions(1, preferredDensity - 2, preferredDensity)
.log(Blocks.OAK_LOG)
.leaves(leaves)
.sapling(Unicopia.id(name + "_sapling"))
.build();
.sapling(Unicopia.id(name + "_sapling"));
}
static void bootstrap() { }
static void bootstrap() {
}
}

View file

@ -5,6 +5,7 @@ import java.util.List;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.block.ShellsBlock;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.server.world.gen.OverworldBiomeSelectionCallback;
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
@ -13,11 +14,13 @@ import net.minecraft.block.Blocks;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.BiomeTags;
import net.minecraft.util.collection.DataPool;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3i;
import net.minecraft.util.math.intprovider.UniformIntProvider;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.gen.GenerationStep;
import net.minecraft.world.gen.blockpredicate.BlockPredicate;
@ -66,6 +69,8 @@ public interface UWorldGen {
BiomePlacementModifier.of()
));
RegistryKey<Biome> SWEET_APPLE_ORCHARD = RegistryKey.of(RegistryKeys.BIOME, Unicopia.id("sweet_apple_orchard"));
static void bootstrap() {
BiomeModifications.addFeature(BiomeSelectors.tag(BiomeTags.IS_JUNGLE), GenerationStep.Feature.VEGETAL_DECORATION, PINEAPPLE_PLANT_PLACED_FEATURE);
BiomeModifications.addFeature(
@ -75,5 +80,11 @@ public interface UWorldGen {
.or(BiomeSelectors.includeByKey(BiomeKeys.STONY_SHORE))
), GenerationStep.Feature.VEGETAL_DECORATION, SHELLS_PLACED_FEATURE);
UTreeGen.bootstrap();
OverworldBiomeSelectionCallback.EVENT.register(context -> {
if (context.biomeKey() == BiomeKeys.FOREST) {
context.addOverride(context.referenceFrame().temperature().splitAbove(0.9F), SWEET_APPLE_ORCHARD);
}
});
}
}

View file

@ -0,0 +1,31 @@
package com.minelittlepony.unicopia.server.world.gen;
import org.jetbrains.annotations.Nullable;
import net.minecraft.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
public interface BiomeSelectionContext {
RegistryKey<Biome> biomeKey();
SplittableBiomeCoordinate referenceFrame();
@Nullable
RegistryKey<Biome> addOverride(SplittableBiomeCoordinate coordinate, RegistryKey<Biome> biome);
public record SplittableBiomeCoordinate(
SplitableParameterRange temperature,
SplitableParameterRange humidity,
SplitableParameterRange continentalness,
SplitableParameterRange erosion,
SplitableParameterRange depth,
SplitableParameterRange weirdness,
long offset) {
}
public interface SplitableParameterRange {
SplittableBiomeCoordinate splitAbove(float midpoint);
SplittableBiomeCoordinate splitBelow(float midpoint);
}
}

View file

@ -0,0 +1,185 @@
package com.minelittlepony.unicopia.server.world.gen;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.include.com.google.common.base.Objects;
import com.minelittlepony.unicopia.server.world.gen.BiomeSelectionContext.SplittableBiomeCoordinate;
import com.mojang.datafixers.util.Pair;
import net.minecraft.registry.RegistryKey;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.util.MultiNoiseUtil;
import net.minecraft.world.biome.source.util.MultiNoiseUtil.NoiseHypercube;
import net.minecraft.world.biome.source.util.MultiNoiseUtil.ParameterRange;
public final class BiomeSelectionInjector implements Consumer<Pair<MultiNoiseUtil.NoiseHypercube, RegistryKey<Biome>>> {
private final Consumer<Pair<MultiNoiseUtil.NoiseHypercube, RegistryKey<Biome>>> parameterConsumer;
public BiomeSelectionInjector(Consumer<Pair<MultiNoiseUtil.NoiseHypercube, RegistryKey<Biome>>> parameterConsumer) {
this.parameterConsumer = parameterConsumer;
}
@Override
public void accept(Pair<NoiseHypercube, RegistryKey<Biome>> parameter) {
Context context = new Context(parameter);
OverworldBiomeSelectionCallback.EVENT.invoker().onSelectingBiome(context);
if (!context.overrides.isEmpty()) {
List<Pair<NoiseHypercube, RegistryKey<Biome>>> subSections = List.of(parameter);
// sort splits from largest to smallest
var divisions = context.overrides.entrySet()
.stream()
.sorted(Comparator.<Map.Entry<SplittableBiomeCoordinate, RegistryKey<Biome>>, Float>comparing(entry -> Context.getVolume(entry.getKey())).reversed())
.toList();
// recursively sub-divide into parts so every split gets a (kinda) fair share
for (Map.Entry<SplittableBiomeCoordinate, RegistryKey<Biome>> division : divisions) {
subSections = subSections.stream().flatMap(par -> Stream.of(
Pair.of(Context.write(division.getKey(), par.getFirst()), division.getValue()),
par
)).toList();
}
// output the result
subSections.forEach(parameterConsumer);
} else {
parameterConsumer.accept(parameter);
}
}
class Context implements BiomeSelectionContext {
private final Pair<NoiseHypercube, RegistryKey<Biome>> parameter;
private final Map<SplittableBiomeCoordinate, RegistryKey<Biome>> overrides = new LinkedHashMap<>();
public Context(Pair<NoiseHypercube, RegistryKey<Biome>> parameter) {
this.parameter = parameter;
}
@Override
public RegistryKey<Biome> biomeKey() {
return parameter.getSecond();
}
@Override
public SplittableBiomeCoordinate referenceFrame() {
return createStart(parameter.getFirst());
}
@Override
@Nullable
public RegistryKey<Biome> addOverride(SplittableBiomeCoordinate coordinate, RegistryKey<Biome> biome) {
return overrides.put(coordinate, biome);
}
static float getVolume(SplittableBiomeCoordinate coordinate) {
return ((SplitableParameterRangeImpl)coordinate.temperature()).length()
* ((SplitableParameterRangeImpl)coordinate.humidity()).length()
* ((SplitableParameterRangeImpl)coordinate.continentalness()).length()
* ((SplitableParameterRangeImpl)coordinate.erosion()).length()
* ((SplitableParameterRangeImpl)coordinate.depth()).length()
* ((SplitableParameterRangeImpl)coordinate.weirdness()).length();
}
static NoiseHypercube write(SplittableBiomeCoordinate coordinate, NoiseHypercube referenceFrame) {
return new NoiseHypercube(
((SplitableParameterRangeImpl)coordinate.temperature()).write(referenceFrame.temperature()),
((SplitableParameterRangeImpl)coordinate.humidity()).write(referenceFrame.humidity()),
((SplitableParameterRangeImpl)coordinate.continentalness()).write(referenceFrame.continentalness()),
((SplitableParameterRangeImpl)coordinate.erosion()).write(referenceFrame.erosion()),
((SplitableParameterRangeImpl)coordinate.depth()).write(referenceFrame.depth()),
((SplitableParameterRangeImpl)coordinate.weirdness()).write(referenceFrame.weirdness()),
coordinate.offset()
);
}
static SplittableBiomeCoordinate createStart(NoiseHypercube referenceFrame) {
final SplittableBiomeCoordinate[] self = new SplittableBiomeCoordinate[1];
return self[0] = new SplittableBiomeCoordinate(
new SplitableParameterRangeImpl(self, SplittableBiomeCoordinate::temperature, 0, 1),
new SplitableParameterRangeImpl(self, SplittableBiomeCoordinate::humidity, 0, 1),
new SplitableParameterRangeImpl(self, SplittableBiomeCoordinate::continentalness, 0, 1),
new SplitableParameterRangeImpl(self, SplittableBiomeCoordinate::erosion, 0, 1),
new SplitableParameterRangeImpl(self, SplittableBiomeCoordinate::depth, 0, 1),
new SplitableParameterRangeImpl(self, SplittableBiomeCoordinate::weirdness, 0, 1),
referenceFrame.offset()
);
}
static SplittableBiomeCoordinate createCopy(SplittableBiomeCoordinate old) {
final SplittableBiomeCoordinate[] self = new SplittableBiomeCoordinate[1];
return self[0] = new SplittableBiomeCoordinate(
new SplitableParameterRangeImpl(self, (SplitableParameterRangeImpl)old.temperature()),
new SplitableParameterRangeImpl(self, (SplitableParameterRangeImpl)old.humidity()),
new SplitableParameterRangeImpl(self, (SplitableParameterRangeImpl)old.continentalness()),
new SplitableParameterRangeImpl(self, (SplitableParameterRangeImpl)old.erosion()),
new SplitableParameterRangeImpl(self, (SplitableParameterRangeImpl)old.depth()),
new SplitableParameterRangeImpl(self, (SplitableParameterRangeImpl)old.weirdness()),
old.offset()
);
}
private static final class SplitableParameterRangeImpl implements SplitableParameterRange {
private final SplittableBiomeCoordinate[] coordinate;
private final Function<SplittableBiomeCoordinate, SplitableParameterRange> dimension;
private float min;
private float max;
SplitableParameterRangeImpl(SplittableBiomeCoordinate[] coordinate, SplitableParameterRangeImpl original) {
this(coordinate, original.dimension, original.min, original.max);
}
SplitableParameterRangeImpl(SplittableBiomeCoordinate[] coordinate, Function<SplittableBiomeCoordinate, SplitableParameterRange> dimension, float min, float max) {
this.coordinate = coordinate;
this.dimension = dimension;
this.min = min;
this.max = max;
}
@Override
public SplittableBiomeCoordinate splitAbove(float midpoint) {
return copyWithDifference(o -> o.min = MathHelper.lerp(midpoint, o.min, o.max));
}
@Override
public SplittableBiomeCoordinate splitBelow(float midpoint) {
return copyWithDifference(o -> o.max = MathHelper.lerp(midpoint, o.min, o.max));
}
private SplittableBiomeCoordinate copyWithDifference(Consumer<SplitableParameterRangeImpl> mutator) {
SplittableBiomeCoordinate copy = createCopy(coordinate[0]);
mutator.accept((SplitableParameterRangeImpl)dimension.apply(copy));
return copy;
}
public ParameterRange write(ParameterRange range) {
return ParameterRange.of(
MultiNoiseUtil.toFloat(range.min()) * (1 + min),
MultiNoiseUtil.toFloat(range.max()) * max
);
}
public float length() {
return max - min;
}
@Override
public boolean equals(Object o) {
return o instanceof SplitableParameterRangeImpl i
&& MathHelper.approximatelyEquals(i.min, min)
&& MathHelper.approximatelyEquals(i.max, max);
}
@Override
public int hashCode() {
return Objects.hashCode(min, max);
}
}
}
}

View file

@ -0,0 +1,51 @@
package com.minelittlepony.unicopia.server.world.gen;
import java.util.HashMap;
import java.util.Map;
import org.joml.Vector2i;
import com.minelittlepony.unicopia.block.FruitBearingBlock;
import com.mojang.datafixers.util.Pair;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.intprovider.IntProvider;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.TestableWorld;
import net.minecraft.world.gen.feature.TreeFeatureConfig;
import net.minecraft.world.gen.foliage.BlobFoliagePlacer;
public class FruitBlobFoliagePlacer extends BlobFoliagePlacer {
public FruitBlobFoliagePlacer(IntProvider radius, IntProvider offset, int height) {
super(radius, offset, height);
}
@Override
public void generate(TestableWorld world, BlockPlacer placer, Random random, TreeFeatureConfig config, int trunkHeight, TreeNode treeNode, int foliageHeight, int radius) {
final Map<Vector2i, Pair<FruitBearingBlock, Integer>> leafPositions = new HashMap<>();
super.generate(world, new BlockPlacer() {
@Override
public void placeBlock(BlockPos pos, BlockState state) {
placer.placeBlock(pos, state);
if (state.getBlock() instanceof FruitBearingBlock block
&& block.isPositionValidForFruit(state, pos)) {
leafPositions.compute(new Vector2i(pos.getX(), pos.getZ()), (col, original) -> original == null || original.getSecond() > pos.getY() ? Pair.of(block, pos.getY()) : original);
}
}
@Override
public boolean hasPlacedBlock(BlockPos pos) {
return placer.hasPlacedBlock(pos);
}
}, random, config, trunkHeight, treeNode, foliageHeight, radius);
BlockPos.Mutable mutable = new BlockPos.Mutable();
leafPositions.entrySet().forEach(pos -> {
mutable.set(pos.getKey().x(), pos.getValue().getSecond() - 1, pos.getKey().y());
if (!placer.hasPlacedBlock(mutable) && random.nextInt(12) == 0) {
placer.placeBlock(mutable, pos.getValue().getFirst().getPlacedFruitState(random));
}
});
}
}

View file

@ -0,0 +1,23 @@
package com.minelittlepony.unicopia.server.world.gen;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Provides a basic event for mods to inject their own biomes in place of vanilla overworld biomes.
* <p>
* Mods are able to insert entries for their biomes into the ranges for existing ones by
* sub-dividing the provided section into pieces and assigning a new biome to one of the create
* segments.
*/
public interface OverworldBiomeSelectionCallback {
Event<OverworldBiomeSelectionCallback> EVENT = EventFactory.createArrayBacked(OverworldBiomeSelectionCallback.class, delegates -> {
return context -> {
for (OverworldBiomeSelectionCallback delegate : delegates) {
delegate.onSelectingBiome(context);
}
};
});
void onSelectingBiome(BiomeSelectionContext context);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"unicopia:sweet_apple_orchard"
]
}

View file

@ -0,0 +1,199 @@
{
"carvers": {
"air": [
"minecraft:cave",
"minecraft:cave_extra_underground",
"minecraft:canyon"
]
},
"downfall": 0.8,
"effects": {
"fog_color": 12638463,
"mood_sound": {
"block_search_extent": 8,
"offset": 2.0,
"sound": "minecraft:ambient.cave",
"tick_delay": 6000
},
"music": {
"max_delay": 24000,
"min_delay": 12000,
"replace_current_music": false,
"sound": "minecraft:music.overworld.forest"
},
"sky_color": 7972607,
"water_color": 4159204,
"water_fog_color": 329011
},
"features": [
[],
[
"minecraft:lake_lava_underground",
"minecraft:lake_lava_surface"
],
[
"minecraft:amethyst_geode"
],
[
"minecraft:monster_room",
"minecraft:monster_room_deep"
],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_gravel",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"minecraft:ore_coal_upper",
"minecraft:ore_coal_lower",
"minecraft:ore_iron_upper",
"minecraft:ore_iron_middle",
"minecraft:ore_iron_small",
"minecraft:ore_gold",
"minecraft:ore_gold_lower",
"minecraft:ore_redstone",
"minecraft:ore_redstone_lower",
"minecraft:ore_diamond",
"minecraft:ore_diamond_large",
"minecraft:ore_diamond_buried",
"minecraft:ore_lapis",
"minecraft:ore_lapis_buried",
"minecraft:ore_copper",
"minecraft:underwater_magma",
"minecraft:disk_sand",
"minecraft:disk_clay",
"minecraft:disk_gravel"
],
[],
[
"minecraft:spring_water",
"minecraft:spring_lava"
],
[
"minecraft:glow_lichen",
"minecraft:forest_flowers",
"minecraft:flower_default",
"minecraft:patch_grass_forest",
"minecraft:brown_mushroom_normal",
"minecraft:red_mushroom_normal",
"minecraft:patch_sugar_cane",
"minecraft:patch_pumpkin"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"spawn_costs": {},
"spawners": {
"ambient": [
{
"type": "minecraft:bat",
"maxCount": 8,
"minCount": 8,
"weight": 10
}
],
"axolotls": [],
"creature": [
{
"type": "minecraft:sheep",
"maxCount": 4,
"minCount": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"maxCount": 4,
"minCount": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"maxCount": 4,
"minCount": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"maxCount": 4,
"minCount": 4,
"weight": 8
},
{
"type": "minecraft:wolf",
"maxCount": 4,
"minCount": 4,
"weight": 5
}
],
"misc": [],
"monster": [
{
"type": "minecraft:spider",
"maxCount": 4,
"minCount": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"maxCount": 4,
"minCount": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"maxCount": 1,
"minCount": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"maxCount": 4,
"minCount": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"maxCount": 4,
"minCount": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"maxCount": 4,
"minCount": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"maxCount": 4,
"minCount": 1,
"weight": 10
},
{
"type": "minecraft:witch",
"maxCount": 1,
"minCount": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"maxCount": 6,
"minCount": 4,
"weight": 10
}
],
"water_ambient": [],
"water_creature": []
},
"temperature": 0.8
}

View file

@ -48,6 +48,7 @@
"MixinStateManagerBuilder",
"MixinBlockState",
"MixinTargetPredicate",
"MixinVanillaBiomeParameters",
"MixinWardenEntity",
"MixinWorld",
"MixinWorldChunk",