From ded8499e9f7537008c79a192683d0431cefb20e0 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 10 Feb 2024 18:16:15 +0000 Subject: [PATCH] Adjust tree spawn rate and add a sweet apple orchard biome --- .../unicopia/block/FruitBearingBlock.java | 6 +- .../unicopia/block/GoldenOakLeavesBlock.java | 2 +- .../mixin/MixinVanillaBiomeParameters.java | 23 ++ .../unicopia/server/world/Tree.java | 62 +++--- .../unicopia/server/world/UTreeGen.java | 42 ++-- .../unicopia/server/world/UWorldGen.java | 11 + .../world/gen/BiomeSelectionContext.java | 31 +++ .../world/gen/BiomeSelectionInjector.java | 185 ++++++++++++++++ .../world/gen/FruitBlobFoliagePlacer.java | 51 +++++ .../gen/OverworldBiomeSelectionCallback.java | 23 ++ .../textures/block/sweet_apple_leaves.png | Bin 2369 -> 7105 bytes .../tags/worldgen/biome/is_forest.json | 6 + .../worldgen/biome/sweet_apple_orchard.json | 199 ++++++++++++++++++ src/main/resources/unicopia.mixin.json | 1 + 14 files changed, 592 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/mixin/MixinVanillaBiomeParameters.java create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionContext.java create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionInjector.java create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/gen/FruitBlobFoliagePlacer.java create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/gen/OverworldBiomeSelectionCallback.java create mode 100644 src/main/resources/data/minecraft/tags/worldgen/biome/is_forest.json create mode 100644 src/main/resources/data/unicopia/worldgen/biome/sweet_apple_orchard.json diff --git a/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java b/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java index b9741cef..54e64851 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java @@ -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; } diff --git a/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java b/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java index 1ae9ed61..fe09f0bd 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java @@ -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); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinVanillaBiomeParameters.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinVanillaBiomeParameters.java new file mode 100644 index 00000000..5a1c0a72 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinVanillaBiomeParameters.java @@ -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>> onWriteOverworldBiomeParameters(Consumer>> parametersCollector) { + return new BiomeSelectionInjector(parametersCollector); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/Tree.java b/src/main/java/com/minelittlepony/unicopia/server/world/Tree.java index 885effb5..7128f919 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/Tree.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/Tree.java @@ -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> configuredFeatureId, - Optional> placedFeatureId, - Optional sapling, - Optional placement + Set placements, + Optional sapling ) { public static final List 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 IS_FOREST = BiomeSelectors.foundInOverworld().and(BiomeSelectors.tag(BiomeTags.IS_FOREST)); + public static final Predicate 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> selector = Optional.empty(); - private Optional countModifier = Optional.empty(); + private Map placements = new HashMap<>(); private Function configParameters = Function.identity(); private Optional 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 selector) { + return placement("", count, extraChance, extraCount, selector); } - public Builder biomes(Predicate selector) { - this.selector = Optional.of(selector); + public Builder placement(String suffex, int count, float extraChance, int extraCount, Predicate 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 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> 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 feature, Predicate selector) { + + } } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/UTreeGen.java b/src/main/java/com/minelittlepony/unicopia/server/world/UTreeGen.java index 0d456e02..7ad5aa26 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/UTreeGen.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/UTreeGen.java @@ -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() { + } } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/UWorldGen.java b/src/main/java/com/minelittlepony/unicopia/server/world/UWorldGen.java index 097fdc9f..69a7991e 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/UWorldGen.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/UWorldGen.java @@ -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 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); + } + }); } } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionContext.java b/src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionContext.java new file mode 100644 index 00000000..1ee71b66 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionContext.java @@ -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 biomeKey(); + + SplittableBiomeCoordinate referenceFrame(); + + @Nullable + RegistryKey addOverride(SplittableBiomeCoordinate coordinate, RegistryKey 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); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionInjector.java b/src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionInjector.java new file mode 100644 index 00000000..1b94b690 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/gen/BiomeSelectionInjector.java @@ -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>> { + + private final Consumer>> parameterConsumer; + + public BiomeSelectionInjector(Consumer>> parameterConsumer) { + this.parameterConsumer = parameterConsumer; + } + + @Override + public void accept(Pair> parameter) { + Context context = new Context(parameter); + OverworldBiomeSelectionCallback.EVENT.invoker().onSelectingBiome(context); + if (!context.overrides.isEmpty()) { + List>> subSections = List.of(parameter); + // sort splits from largest to smallest + var divisions = context.overrides.entrySet() + .stream() + .sorted(Comparator.>, 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> 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> parameter; + private final Map> overrides = new LinkedHashMap<>(); + + public Context(Pair> parameter) { + this.parameter = parameter; + } + + @Override + public RegistryKey biomeKey() { + return parameter.getSecond(); + } + + @Override + public SplittableBiomeCoordinate referenceFrame() { + return createStart(parameter.getFirst()); + } + + @Override + @Nullable + public RegistryKey addOverride(SplittableBiomeCoordinate coordinate, RegistryKey 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 dimension; + private float min; + private float max; + + SplitableParameterRangeImpl(SplittableBiomeCoordinate[] coordinate, SplitableParameterRangeImpl original) { + this(coordinate, original.dimension, original.min, original.max); + } + + SplitableParameterRangeImpl(SplittableBiomeCoordinate[] coordinate, Function 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 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); + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/gen/FruitBlobFoliagePlacer.java b/src/main/java/com/minelittlepony/unicopia/server/world/gen/FruitBlobFoliagePlacer.java new file mode 100644 index 00000000..db32594e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/gen/FruitBlobFoliagePlacer.java @@ -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> 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)); + } + }); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/gen/OverworldBiomeSelectionCallback.java b/src/main/java/com/minelittlepony/unicopia/server/world/gen/OverworldBiomeSelectionCallback.java new file mode 100644 index 00000000..b98f6826 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/gen/OverworldBiomeSelectionCallback.java @@ -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. + *

+ * 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 EVENT = EventFactory.createArrayBacked(OverworldBiomeSelectionCallback.class, delegates -> { + return context -> { + for (OverworldBiomeSelectionCallback delegate : delegates) { + delegate.onSelectingBiome(context); + } + }; + }); + + void onSelectingBiome(BiomeSelectionContext context); +} diff --git a/src/main/resources/assets/unicopia/textures/block/sweet_apple_leaves.png b/src/main/resources/assets/unicopia/textures/block/sweet_apple_leaves.png index 2f7822b4ea7d0ce836251ebd11b14f26541159cb..46bad15f8f6f64c0f642471141859960ec4bddb3 100644 GIT binary patch literal 7105 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 delta 2359 zcmV-73CQ-rH^CB+BYy?KdQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+O1dFvF)%8 z`&ShyK_s=7qje@7r2IfOsdKl{FXke_xrR1Zqh!wqsV=GVJ*V-S3lz^c*@&(_mi`be1Dmc?vwuNr}(2^?l$}_ zqGAg%t}p$m`s}~{%IDsDmW`a<868?kz$a-^mdew@zz$&KnU-*6+!5{kToqUG%@0Sx zPd|LeHK-Dk_)UQ(HQKbE=b(WQ124{aG7pxguSn*~L%fg#CqV*j&QfJc5thMA1igG? zp=aOvY&WQ2<9|sQ$%MrSGoH&?*o)|Vv&GR2j+qVoA2)uJ<_MP#63g{21Y!?J2_8X01#pCh+s&-fKMcm68X%WiGX7P zKMj$yxWFA0P>ehgB?plNatfclF(=bAmLtF2kPwK7l7A$Fnko%kg%Cq>A%}t*YEfc{ zF{YSfi8Zz)DWsTE%IRpaWyvAOoN~@3*W8MfP-00Xmr`nJu5uMp z?Q}>|nL&S&4x&-RR(XV%32rgNyJu5&d>i8y|8#}zl zIXP;xs8Q*bUw!w3?-qUSwMwvw73_HH)PH@!)kEz@L$`4_Y>mn;7Bh5faA+AoBD-od zIOmgQfbW2!E0wX8zM9_veF(Pp)ynp+8vHvKTP0s*_{Hz+b9MwaWG9)(R4~jg6pbw% zzxk=yTdOp-_0@XCRI$)#2+;&!!0Rv_^aX?s3-OI{|EOZ{4zwg(RR{6Nd z3mz=JbN)Y&2`Rs&Tp3XNtWo2FbVrR{uc8?Fz7(H-o?!D2?{?qx?^t<(d=UbAP7urxvDa z%}ycga9?WcXwJ$5HgiwbwYDXv4vjCi$@c#4Jle(CiURUA#Miu*zr_UXXf68bcWW3W zVQD9BGXy{OEfBI??Ck%_XSjIjRD3!rj^v6L7r{s~N-F zmZ6p1{C>_I>Mf~|u5aXU53&zYv}U|tWX{+P{nmB2q|~NR&iG!$-~I02qyoNNJPlC@ z0009!Nkl%J?64-d4Zkn9d&S+ zUzjHyq-;Ll)a5J7^5oYiIG3(m!Wg52!GOv{h4*jYQyeMcc^(L&R;f0sc!5WAyGgs< zrdTWzwIV#n!|4rrG-etEeju4EUJ9jmluJYpX2BV*iN>~fY@=Q%; zi1s2}*G1OX*R>tB`S{OAE-qh`?WfzCJ2~aCGN-Gj34#E%O0(Id-|J&jD?u?(_n3?0 z2u_kDAh2|GiGQdSVX@--KDm=)GmEtrfZ=dRnxvp83WZXE`nd+q?$$1qiHcnR>bgwL zPN`q=H7EvTS;oP^0lV9~7-Oi{>zq7!62IWn>-I>Jg!Wz=XJl-IcGT7zU*FLEzxJ7( zpCvUZi;IhNKXoYvMcjhR&L2C(am?JsIhL+22|zdQ;(zq}eKb?XzZhq-Hp%{<`%KSF z%i-Z6iWgqsV`6Ac^-L|V+MwYKRgr>(Y@V0 z1qf>)aepVK;1$^TZ9~srI4=%BX|zOd&|`9T5_`-N_hXLYBUY}hU@}9cTETG~3Vwh( zN@`M)VZ!F~O?7U6dt1|fN^QE%%cn0ncm5o{?{hpjjyh;