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 2f7822b4..46bad15f 100644 Binary files a/src/main/resources/assets/unicopia/textures/block/sweet_apple_leaves.png and b/src/main/resources/assets/unicopia/textures/block/sweet_apple_leaves.png differ diff --git a/src/main/resources/data/minecraft/tags/worldgen/biome/is_forest.json b/src/main/resources/data/minecraft/tags/worldgen/biome/is_forest.json new file mode 100644 index 00000000..fec63434 --- /dev/null +++ b/src/main/resources/data/minecraft/tags/worldgen/biome/is_forest.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "unicopia:sweet_apple_orchard" + ] +} diff --git a/src/main/resources/data/unicopia/worldgen/biome/sweet_apple_orchard.json b/src/main/resources/data/unicopia/worldgen/biome/sweet_apple_orchard.json new file mode 100644 index 00000000..f9a23e67 --- /dev/null +++ b/src/main/resources/data/unicopia/worldgen/biome/sweet_apple_orchard.json @@ -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 +} \ No newline at end of file diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index dc5da585..81c55c34 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -48,6 +48,7 @@ "MixinStateManagerBuilder", "MixinBlockState", "MixinTargetPredicate", + "MixinVanillaBiomeParameters", "MixinWardenEntity", "MixinWorld", "MixinWorldChunk",