- ) tag;
}
/**
* Adds {@link Block} to NETHER_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
+ *
* @param block - {@link Block}.
*/
public static void addNetherGround(Block block) {
- TagHelper.addTag(NETHER_GROUND, block);
- TagHelper.addTag(GEN_TERRAIN, block);
+ addTag(BLOCK_NETHER_GROUND, block);
+ addTag(BLOCK_GEN_TERRAIN, block);
}
/**
* Adds {@link Block} to END_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
+ *
* @param block - {@link Block}.
*/
public static void addEndGround(Block block) {
- TagHelper.addTag(GEN_TERRAIN, block);
- TagHelper.addTag(END_GROUND, block);
+ addTag(BLOCK_GEN_TERRAIN, block);
+ addTag(BLOCK_END_GROUND, block);
}
/**
* Initializes basic tags. Should be called only in BCLib main class.
*/
public static void init() {
- TagHelper.addTag(BOOKSHELVES, Blocks.BOOKSHELF);
- TagHelper.addTag(GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
- TagHelper.addTag(NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
- TagHelper.addTag(END_GROUND, Blocks.END_STONE);
- TagHelper.addTag(BLOCK_CHEST, Blocks.CHEST);
- TagHelper.addTag(ITEM_CHEST, Items.CHEST);
- TagHelper.addTag(IRON_INGOTS, Items.IRON_INGOT);
- TagHelper.addTag(FURNACES, Blocks.FURNACE);
+ addTag(BLOCK_BOOKSHELVES, Blocks.BOOKSHELF);
+ addTag(BLOCK_GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
+ addTag(BLOCK_NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
+ addTag(BLOCK_END_GROUND, Blocks.END_STONE);
+ addTag(BLOCK_CHEST, Blocks.CHEST);
+ addTag(ITEM_CHEST, Items.CHEST);
+ addTag(ITEM_IRON_INGOTS, Items.IRON_INGOT);
+ addTag(ITEM_FURNACES, Blocks.FURNACE);
+ }
+
+ /**
+ * Adds one Tag to multiple Blocks.
+ *
+ * Example:
+ *
{@code Tag.Named DIMENSION_STONE = makeBlockTag("mymod", "dim_stone");
+ * addTag(DIMENSION_STONE, Blocks.END_STONE, Blocks.NETHERRACK);}
+ *
+ * The call will reserve the Tag. The Tag is added to the blocks once
+ * {@link #apply(String, Map)} was executed.
+ *
+ * @param tag The new Tag
+ * @param blocks One or more blocks that should receive the Tag.
+ */
+ public static void addTag(Tag.Named tag, Block... blocks) {
+ ResourceLocation tagID = tag.getName();
+ Set set = TAGS_BLOCK.computeIfAbsent(tagID, k -> Sets.newHashSet());
+ for (Block block : blocks) {
+ ResourceLocation id = Registry.BLOCK.getKey(block);
+ if (id != Registry.BLOCK.getDefaultKey()) {
+ set.add(id);
+ }
+ }
+ }
+
+ /**
+ * Adds one Tag to multiple Items.
+ *
+ * Example:
+ *
{@code Tag.Named- METALS = makeBlockTag("mymod", "metals");
+ * addTag(METALS, Items.IRON_INGOT, Items.GOLD_INGOT, Items.COPPER_INGOT);}
+ *
+ * The call will reserve the Tag. The Tag is added to the items once
+ * {@link #apply(String, Map)} was executed.
+ *
+ * @param tag The new Tag
+ * @param items One or more item that should receive the Tag.
+ */
+ public static void addTag(Tag.Named- tag, ItemLike... items) {
+ ResourceLocation tagID = tag.getName();
+ Set set = TAGS_ITEM.computeIfAbsent(tagID, k -> Sets.newHashSet());
+ for (ItemLike item : items) {
+ ResourceLocation id = Registry.ITEM.getKey(item.asItem());
+ if (id != Registry.ITEM.getDefaultKey()) {
+ set.add(id);
+ }
+ }
+ }
+
+ /**
+ * Adds multiple Tags to one Item.
+ *
+ * The call will reserve the Tags. The Tags are added to the Item once
+ * * {@link #apply(String, Map)} was executed.
+ *
+ * @param item The Item that will receive all Tags
+ * @param tags One or more Tags
+ */
+ @SafeVarargs
+ public static void addTags(ItemLike item, Tag.Named- ... tags) {
+ for (Tag.Named
- tag : tags) {
+ addTag(tag, item);
+ }
+ }
+
+ /**
+ * Adds multiple Tags to one Block.
+ *
+ * The call will reserve the Tags. The Tags are added to the Block once
+ * * {@link #apply(String, Map)} was executed.
+ *
+ * @param block The Block that will receive all Tags
+ * @param tags One or more Tags
+ */
+ @SafeVarargs
+ public static void addTags(Block block, Tag.Named... tags) {
+ for (Tag.Named tag : tags) {
+ addTag(tag, block);
+ }
+ }
+
+ /**
+ * Adds all {@code ids} to the {@code builder}.
+ *
+ * @param builder
+ * @param ids
+ * @return The Builder passed as {@code builder}.
+ */
+ public static Tag.Builder apply(Tag.Builder builder, Set ids) {
+ ids.forEach(value -> builder.addElement(value, "Better End Code"));
+ return builder;
+ }
+
+ /**
+ * Automatically called in {@link net.minecraft.tags.TagLoader#loadAndBuild(ResourceManager)}.
+ *
+ * In most cases there is no need to call this Method manually.
+ *
+ * @param directory The name of the Tag-directory. Should be either "tags/blocks" or
+ * "tags/items".
+ * @param tagsMap The map that will hold the registered Tags
+ * @return The {@code tagsMap} Parameter.
+ */
+ public static Map apply(String directory, Map tagsMap) {
+ Map> endTags = null;
+ if ("tags/blocks".equals(directory)) {
+ endTags = TAGS_BLOCK;
+ }
+ else if ("tags/items".equals(directory)) {
+ endTags = TAGS_ITEM;
+ }
+ if (endTags != null) {
+ endTags.forEach((id, ids) -> apply(tagsMap.computeIfAbsent(id, key -> Tag.Builder.tag()), ids));
+ }
+ return tagsMap;
}
}
diff --git a/src/main/java/ru/bclib/api/WorldDataAPI.java b/src/main/java/ru/bclib/api/WorldDataAPI.java
index 6927c9ed..d15cf43b 100644
--- a/src/main/java/ru/bclib/api/WorldDataAPI.java
+++ b/src/main/java/ru/bclib/api/WorldDataAPI.java
@@ -1,20 +1,30 @@
package ru.bclib.api;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtIo;
+import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
+import ru.bclib.BCLib;
+import ru.bclib.api.datafixer.DataFixerAPI;
+import ru.bclib.util.ModUtil;
+
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Consumer;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import net.fabricmc.loader.api.FabricLoader;
-import net.fabricmc.loader.api.ModContainer;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.NbtIo;
-import ru.bclib.BCLib;
-
+/**
+ * Mod-specifix data-storage for a world.
+ *
+ * This class provides the ability for mod to store persistent data inside a world. The Storage for the world is
+ * currently initialized as part of the {@link DataFixerAPI} in {@link DataFixerAPI#fixData(LevelStorageAccess, boolean, Consumer)}
+ * or {@link DataFixerAPI#initializeWorldData(File, boolean)}
+ */
public class WorldDataAPI {
private static final Map TAGS = Maps.newHashMap();
private static final List MODS = Lists.newArrayList();
@@ -22,36 +32,43 @@ public class WorldDataAPI {
public static void load(File dataDir) {
WorldDataAPI.dataDir = dataDir;
- MODS.stream().parallel().forEach(modID -> {
- File file = new File(dataDir, modID + ".nbt");
- CompoundTag root = new CompoundTag();
- TAGS.put(modID, root);
- if (file.exists()) {
- try {
- root = NbtIo.readCompressed(file);
- }
- catch (IOException e) {
- BCLib.LOGGER.error("World data loading failed", e);
- }
- }
- else {
- Optional optional = FabricLoader.getInstance().getModContainer(modID);
- if (optional.isPresent()) {
- ModContainer modContainer = optional.get();
- if (BCLib.isDevEnvironment()) {
- root.putString("version", "63.63.63");
+ MODS.stream()
+ .parallel()
+ .forEach(modID -> {
+ File file = new File(dataDir, modID + ".nbt");
+ CompoundTag root = new CompoundTag();
+ if (file.exists()) {
+ try {
+ root = NbtIo.readCompressed(file);
}
- else {
- root.putString("version", modContainer.getMetadata().getVersion().toString());
+ catch (IOException e) {
+ BCLib.LOGGER.error("World data loading failed", e);
}
- saveFile(modID);
+ TAGS.put(modID, root);
}
- }
- });
+ else {
+ Optional optional = FabricLoader.getInstance()
+ .getModContainer(modID);
+ if (optional.isPresent()) {
+ ModContainer modContainer = optional.get();
+ if (BCLib.isDevEnvironment()) {
+ root.putString("version", "255.255.9999");
+ }
+ else {
+ root.putString("version", modContainer.getMetadata()
+ .getVersion()
+ .toString());
+ }
+ TAGS.put(modID, root);
+ saveFile(modID);
+ }
+ }
+ });
}
/**
* Register mod cache, world cache is located in world data folder.
+ *
* @param modID - {@link String} modID.
*/
public static void registerModCache(String modID) {
@@ -60,6 +77,7 @@ public class WorldDataAPI {
/**
* Get root {@link CompoundTag} for mod cache in world data folder.
+ *
* @param modID - {@link String} modID.
* @return {@link CompoundTag}
*/
@@ -74,13 +92,14 @@ public class WorldDataAPI {
/**
* Get {@link CompoundTag} with specified path from mod cache in world data folder.
+ *
* @param modID - {@link String} path to tag, dot-separated.
* @return {@link CompoundTag}
*/
public static CompoundTag getCompoundTag(String modID, String path) {
String[] parts = path.split("\\.");
CompoundTag tag = getRootTag(modID);
- for (String part: parts) {
+ for (String part : parts) {
if (tag.contains(part)) {
tag = tag.getCompound(part);
}
@@ -95,10 +114,14 @@ public class WorldDataAPI {
/**
* Forces mod cache file to be saved.
+ *
* @param modID {@link String} mod ID.
*/
public static void saveFile(String modID) {
try {
+ if (!dataDir.exists()) {
+ dataDir.mkdirs();
+ }
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));
}
catch (IOException e) {
@@ -108,6 +131,7 @@ public class WorldDataAPI {
/**
* Get stored mod version (only for mods with registered cache).
+ *
* @return {@link String} mod version.
*/
public static String getModVersion(String modID) {
@@ -116,9 +140,10 @@ public class WorldDataAPI {
/**
* Get stored mod version as integer (only for mods with registered cache).
+ *
* @return {@code int} mod version.
*/
public static int getIntModVersion(String modID) {
- return DataFixerAPI.getModVersion(getModVersion(modID));
+ return ModUtil.convertModVersion(getModVersion(modID));
}
}
diff --git a/src/main/java/ru/bclib/api/biomes/BCLBiomeBuilder.java b/src/main/java/ru/bclib/api/biomes/BCLBiomeBuilder.java
new file mode 100644
index 00000000..eb53ef05
--- /dev/null
+++ b/src/main/java/ru/bclib/api/biomes/BCLBiomeBuilder.java
@@ -0,0 +1,599 @@
+package ru.bclib.api.biomes;
+
+import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
+import net.minecraft.core.particles.ParticleOptions;
+import net.minecraft.data.BuiltinRegistries;
+import net.minecraft.data.worldgen.BiomeDefaultFeatures;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.sounds.Music;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.level.biome.AmbientAdditionsSettings;
+import net.minecraft.world.level.biome.AmbientMoodSettings;
+import net.minecraft.world.level.biome.AmbientParticleSettings;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.biome.Biome.BiomeBuilder;
+import net.minecraft.world.level.biome.Biome.BiomeCategory;
+import net.minecraft.world.level.biome.Biome.Precipitation;
+import net.minecraft.world.level.biome.BiomeGenerationSettings;
+import net.minecraft.world.level.biome.BiomeSpecialEffects;
+import net.minecraft.world.level.biome.MobSpawnSettings;
+import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.levelgen.GenerationStep;
+import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
+import net.minecraft.world.level.levelgen.Noises;
+import net.minecraft.world.level.levelgen.SurfaceRules;
+import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
+import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.level.levelgen.placement.PlacedFeature;
+import ru.bclib.util.ColorUtil;
+import ru.bclib.world.biomes.BCLBiome;
+import ru.bclib.world.features.BCLFeature;
+import ru.bclib.world.structures.BCLStructureFeature;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+
+public class BCLBiomeBuilder {
+ private static final BCLBiomeBuilder INSTANCE = new BCLBiomeBuilder();
+ private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition(Noises.SOUL_SAND_LAYER, -0.012);
+
+ private List structures = new ArrayList<>(16);
+ private BiomeGenerationSettings.Builder generationSettings;
+ private BiomeSpecialEffects.Builder effectsBuilder;
+ private MobSpawnSettings.Builder spawnSettings;
+ private SurfaceRules.RuleSource surfaceRule;
+ private Precipitation precipitation;
+ private ResourceLocation biomeID;
+ private BiomeCategory category;
+ private float temperature;
+ private float fogDensity;
+ private float downfall;
+
+ /**
+ * Starts new biome building process.
+ * @param biomeID {@link ResourceLocation} biome identifier.
+ * @return prepared {@link BCLBiomeBuilder} instance.
+ */
+ public static BCLBiomeBuilder start(ResourceLocation biomeID) {
+ INSTANCE.biomeID = biomeID;
+ INSTANCE.precipitation = Precipitation.NONE;
+ INSTANCE.category = BiomeCategory.NONE;
+ INSTANCE.generationSettings = null;
+ INSTANCE.effectsBuilder = null;
+ INSTANCE.spawnSettings = null;
+ INSTANCE.structures.clear();
+ INSTANCE.temperature = 1.0F;
+ INSTANCE.fogDensity = 1.0F;
+ INSTANCE.downfall = 1.0F;
+ return INSTANCE;
+ }
+
+ /**
+ * Set biome {@link Precipitation}. Affect biome visual effects (rain, snow, none).
+ * @param precipitation {@link Precipitation}
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder precipitation(Precipitation precipitation) {
+ this.precipitation = precipitation;
+ return this;
+ }
+
+ /**
+ * Set biome category. Doesn't affect biome worldgen, but Fabric biome modifications can target biome by it.
+ * @param category {@link BiomeCategory}
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder category(BiomeCategory category) {
+ this.category = category;
+ return this;
+ }
+
+ /**
+ * Set biome temperature, affect plant color, biome generation and ice formation.
+ * @param temperature biome temperature.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder temperature(float temperature) {
+ this.temperature = temperature;
+ return this;
+ }
+
+ /**
+ * Set biome wetness (same as downfall). Affect plant color and biome generation.
+ * @param wetness biome wetness (downfall).
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder wetness(float wetness) {
+ this.downfall = wetness;
+ return this;
+ }
+
+ /**
+ * Adds mob spawning to biome.
+ * @param entityType {@link EntityType} mob type.
+ * @param weight spawn weight.
+ * @param minGroupCount minimum mobs in group.
+ * @param maxGroupCount maximum mobs in group.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder spawn(EntityType entityType, int weight, int minGroupCount, int maxGroupCount) {
+ getSpawns().addSpawn(entityType.getCategory(), new SpawnerData(entityType, weight, minGroupCount, maxGroupCount));
+ return this;
+ }
+
+ /**
+ * Adds ambient particles to thr biome.
+ * @param particle {@link ParticleOptions} particles (or {@link net.minecraft.core.particles.ParticleType}).
+ * @param probability particle spawn probability, should have low value (example: 0.01F).
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder particles(ParticleOptions particle, float probability) {
+ getEffects().ambientParticle(new AmbientParticleSettings(particle, probability));
+ return this;
+ }
+
+ /**
+ * Sets sky color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder skyColor(int color) {
+ getEffects().skyColor(color);
+ return this;
+ }
+
+ /**
+ * Sets sky color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder skyColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return skyColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets fog color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder fogColor(int color) {
+ getEffects().fogColor(color);
+ return this;
+ }
+
+ /**
+ * Sets fog color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder fogColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return fogColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets fog density for the biome.
+ * @param density fog density as a float, default value is 1.0F.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder fogDensity(float density) {
+ this.fogDensity = density;
+ return this;
+ }
+
+ /**
+ * Sets water color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder waterColor(int color) {
+ getEffects().waterColor(color);
+ return this;
+ }
+
+ /**
+ * Sets water color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder waterColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return waterColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets underwater fog color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder waterFogColor(int color) {
+ getEffects().waterFogColor(color);
+ return this;
+ }
+
+ /**
+ * Sets underwater fog color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder waterFogColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return waterFogColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets water and underwater fig color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder waterAndFogColor(int color) {
+ return waterColor(color).waterFogColor(color);
+ }
+
+ /**
+ * Sets water and underwater fig color for the biome. Color is in ARGB int format.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder waterAndFogColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return waterAndFogColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets grass color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder grassColor(int color) {
+ getEffects().grassColorOverride(color);
+ return this;
+ }
+
+ /**
+ * Sets grass color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder grassColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return grassColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets leaves and plants color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder foliageColor(int color) {
+ getEffects().foliageColorOverride(color);
+ return this;
+ }
+
+ /**
+ * Sets leaves and plants color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder foliageColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return foliageColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets grass, leaves and all plants color for the biome. Color is in ARGB int format.
+ * @param color ARGB color as integer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder plantsColor(int color) {
+ return grassColor(color).foliageColor(color);
+ }
+
+ /**
+ * Sets grass, leaves and all plants color for the biome. Color represented as red, green and blue channel values.
+ * @param red red color component [0-255]
+ * @param green green color component [0-255]
+ * @param blue blue color component [0-255]
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder plantsColor(int red, int green, int blue) {
+ red = Mth.clamp(red, 0, 255);
+ green = Mth.clamp(green, 0, 255);
+ blue = Mth.clamp(blue, 0, 255);
+ return plantsColor(ColorUtil.color(red, green, blue));
+ }
+
+ /**
+ * Sets biome music, used for biomes in the Nether and End.
+ * @param music {@link Music} to use.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder music(Music music) {
+ getEffects().backgroundMusic(music);
+ return this;
+ }
+
+ /**
+ * Sets biome music, used for biomes in the Nether and End.
+ * @param music {@link SoundEvent} to use.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder music(SoundEvent music) {
+ return music(new Music(music, 600, 2400, true));
+ }
+
+ /**
+ * Sets biome ambient loop sound. Can be used for biome environment.
+ * @param loopSound {@link SoundEvent} to use as a loop.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder loop(SoundEvent loopSound) {
+ getEffects().ambientLoopSound(loopSound);
+ return this;
+ }
+
+ /**
+ * Sets biome mood sound. Can be used for biome environment.
+ * @param mood {@link SoundEvent} to use as a mood.
+ * @param tickDelay delay between sound events in ticks.
+ * @param blockSearchExtent block search radius (for area available for sound).
+ * @param soundPositionOffset offset in sound.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder mood(SoundEvent mood, int tickDelay, int blockSearchExtent, float soundPositionOffset) {
+ getEffects().ambientMoodSound(new AmbientMoodSettings(mood, tickDelay, blockSearchExtent, soundPositionOffset));
+ return this;
+ }
+
+ /**
+ * Sets biome mood sound. Can be used for biome environment.
+ * @param mood {@link SoundEvent} to use as a mood.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder mood(SoundEvent mood) {
+ return mood(mood, 6000, 8, 2.0F);
+ }
+
+ /**
+ * Sets biome additionsl ambient sounds.
+ * @param additions {@link SoundEvent} to use.
+ * @param intensity sound intensity. Default is 0.0111F.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder additions(SoundEvent additions, float intensity) {
+ getEffects().ambientAdditionsSound(new AmbientAdditionsSettings(additions, intensity));
+ return this;
+ }
+
+ /**
+ * Sets biome additionsl ambient sounds.
+ * @param additions {@link SoundEvent} to use.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder additions(SoundEvent additions) {
+ return additions(additions, 0.0111F);
+ }
+
+ /**
+ * Adds new feature to the biome.
+ * @param decoration {@link Decoration} feature step.
+ * @param feature {@link PlacedFeature}.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder feature(Decoration decoration, PlacedFeature feature) {
+ getGeneration().addFeature(decoration, feature);
+ return this;
+ }
+
+ /**
+ * Adds vanilla Mushrooms.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder defaultMushrooms() {
+ return feature(BiomeDefaultFeatures::addDefaultMushrooms);
+ }
+
+ /**
+ * Adds vanilla Nether Ores.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder netherDefaultOres() {
+ return feature(BiomeDefaultFeatures::addNetherDefaultOres);
+ }
+
+ /**
+ * Will add features into biome, used for vanilla feature adding functions.
+ * @param featureAdd {@link Consumer} with {@link BiomeGenerationSettings.Builder}.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder feature(Consumer featureAdd) {
+ featureAdd.accept(getGeneration());
+ return this;
+ }
+
+ /**
+ * Adds new feature to the biome.
+ * @param feature {@link BCLFeature}.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder feature(BCLFeature feature) {
+ return feature(feature.getDecoration(), feature.getPlacedFeature());
+ }
+
+ /**
+ * Adds new structure feature into the biome.
+ * @param structure {@link ConfiguredStructureFeature} to add.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder structure(ConfiguredStructureFeature, ?> structure) {
+ structures.add(structure);
+ return this;
+ }
+
+ /**
+ * Adds new structure feature into thr biome. Will add building biome into the structure list.
+ * @param structure {@link BCLStructureFeature} to add.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder structure(BCLStructureFeature structure) {
+ structure.addInternalBiome(biomeID);
+ return structure(structure.getFeatureConfigured());
+ }
+
+ /**
+ * Adds new world carver into the biome.
+ * @param carver {@link ConfiguredWorldCarver} to add.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder carver(GenerationStep.Carving step, ConfiguredWorldCarver> carver) {
+ BuiltinRegistries.CONFIGURED_CARVER
+ .getResourceKey(carver)
+ .ifPresent(key -> BiomeModifications.addCarver(ctx -> ctx.getBiomeKey().location().equals(biomeID), step, key));
+ return this;
+ }
+
+ /**
+ * Adds new world surface rule for the given block
+ * @param surfaceBlock {@link Block} to use.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder surface(Block surfaceBlock) {
+ return surface(SurfaceRuleBuilder.start().surface(surfaceBlock.defaultBlockState()).build());
+ }
+
+ /**
+ * Adds blocks to the biome surface and below it (with specified depth).
+ * @param surfaceBlock {@link Block} that will cover biome.
+ * @param subterrainBlock {@link Block} below it with specified depth.
+ * @param depth thickness of bottom block layer.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder surface(Block surfaceBlock, Block subterrainBlock, int depth) {
+ return surface(SurfaceRuleBuilder
+ .start()
+ .surface(surfaceBlock.defaultBlockState())
+ .subsurface(subterrainBlock.defaultBlockState(), depth)
+ .build());
+ }
+
+ /**
+ * Adds surface rule to this biome.
+ * @param surfaceRule {link SurfaceRules.RuleSource} surface rule.
+ * @return same {@link BCLBiomeBuilder} instance.
+ */
+ public BCLBiomeBuilder surface(SurfaceRules.RuleSource surfaceRule) {
+ this.surfaceRule = surfaceRule;
+ return this;
+ }
+
+ /**
+ * Finalize biome creation.
+ * @return created {@link BCLBiome} instance.
+ */
+ public BCLBiome build() {
+ return build(BCLBiome::new);
+ }
+
+ /**
+ * Finalize biome creation.
+ * @param biomeConstructor {@link BiFunction} biome constructor.
+ * @return created {@link BCLBiome} instance.
+ */
+ public T build(BiFunction biomeConstructor) {
+ BiomeBuilder builder = new BiomeBuilder()
+ .precipitation(precipitation)
+ .biomeCategory(category)
+ .temperature(temperature)
+ .downfall(downfall);
+
+ if (spawnSettings != null) {
+ builder.mobSpawnSettings(spawnSettings.build());
+ }
+
+ if (effectsBuilder != null) {
+ builder.specialEffects(effectsBuilder.build());
+ }
+
+ if (generationSettings != null) {
+ builder.generationSettings(generationSettings.build());
+ }
+
+ final T res = biomeConstructor.apply(biomeID, builder.build());
+ res.attachStructures(structures);
+ res.setSurface(surfaceRule);
+ res.setFogDensity(fogDensity);
+ return res;
+ }
+
+ /**
+ * Get or create {@link BiomeSpecialEffects.Builder} for biome visual effects.
+ * For internal usage only.
+ * For internal usage only.
+ * @return new or same {@link BiomeSpecialEffects.Builder} instance.
+ */
+ private BiomeSpecialEffects.Builder getEffects() {
+ if (effectsBuilder == null) {
+ effectsBuilder = new BiomeSpecialEffects.Builder();
+ }
+ return effectsBuilder;
+ }
+
+ /**
+ * Get or create {@link MobSpawnSettings.Builder} for biome mob spawning.
+ * For internal usage only.
+ * @return new or same {@link MobSpawnSettings.Builder} instance.
+ */
+ private MobSpawnSettings.Builder getSpawns() {
+ if (spawnSettings == null) {
+ spawnSettings = new MobSpawnSettings.Builder();
+ }
+ return spawnSettings;
+ }
+
+ /**
+ * Get or create {@link BiomeGenerationSettings.Builder} for biome features and generation.
+ * For internal usage only.
+ * @return new or same {@link BiomeGenerationSettings.Builder} instance.
+ */
+ private BiomeGenerationSettings.Builder getGeneration() {
+ if (generationSettings == null) {
+ generationSettings = new BiomeGenerationSettings.Builder();
+ }
+ return generationSettings;
+ }
+}
diff --git a/src/main/java/ru/bclib/api/biomes/BiomeAPI.java b/src/main/java/ru/bclib/api/biomes/BiomeAPI.java
new file mode 100644
index 00000000..9bad5eee
--- /dev/null
+++ b/src/main/java/ru/bclib/api/biomes/BiomeAPI.java
@@ -0,0 +1,728 @@
+package ru.bclib.api.biomes;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.impl.biome.NetherBiomeData;
+import net.fabricmc.fabric.impl.biome.TheEndBiomeData;
+import net.minecraft.client.Minecraft;
+import net.minecraft.core.Registry;
+import net.minecraft.data.BuiltinRegistries;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.random.WeightedRandomList;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.MobCategory;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.biome.BiomeSource;
+import net.minecraft.world.level.biome.Biomes;
+import net.minecraft.world.level.biome.Climate;
+import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
+import net.minecraft.world.level.levelgen.GenerationStep.Carving;
+import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
+import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
+import net.minecraft.world.level.levelgen.SurfaceRules;
+import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
+import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.level.levelgen.feature.StructureFeature;
+import net.minecraft.world.level.levelgen.placement.PlacedFeature;
+import org.jetbrains.annotations.Nullable;
+import ru.bclib.BCLib;
+import ru.bclib.config.Configs;
+import ru.bclib.interfaces.SurfaceRuleProvider;
+import ru.bclib.mixin.common.BiomeGenerationSettingsAccessor;
+import ru.bclib.mixin.common.MobSpawnSettingsAccessor;
+import ru.bclib.mixin.common.StructureSettingsAccessor;
+import ru.bclib.util.CollectionsUtil;
+import ru.bclib.util.MHelper;
+import ru.bclib.world.biomes.BCLBiome;
+import ru.bclib.world.biomes.FabricBiomesData;
+import ru.bclib.world.features.BCLFeature;
+import ru.bclib.world.generator.BiomePicker;
+import ru.bclib.world.structures.BCLStructureFeature;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class BiomeAPI {
+ /**
+ * Empty biome used as default value if requested biome doesn't exist or linked. Shouldn't be registered anywhere to prevent bugs.
+ * Have {@code Biomes.THE_VOID} as the reference biome.
+ */
+ public static final BCLBiome EMPTY_BIOME = new BCLBiome(Biomes.THE_VOID.location());
+
+ public static final BiomePicker NETHER_BIOME_PICKER = new BiomePicker();
+ public static final BiomePicker END_LAND_BIOME_PICKER = new BiomePicker();
+ public static final BiomePicker END_VOID_BIOME_PICKER = new BiomePicker();
+
+ private static final Map ID_MAP = Maps.newHashMap();
+ private static final Map CLIENT = Maps.newHashMap();
+ private static Registry biomeRegistry;
+
+ private static final Map>> MODIFICATIONS = Maps.newHashMap();
+ private static final Map SURFACE_RULES = Maps.newHashMap();
+ private static final Set MODIFIED_BIOMES = Sets.newHashSet();
+
+ public static final BCLBiome NETHER_WASTES_BIOME = registerNetherBiome(getFromRegistry(Biomes.NETHER_WASTES));
+ public static final BCLBiome CRIMSON_FOREST_BIOME = registerNetherBiome(getFromRegistry(Biomes.CRIMSON_FOREST));
+ public static final BCLBiome WARPED_FOREST_BIOME = registerNetherBiome(getFromRegistry(Biomes.WARPED_FOREST));
+ public static final BCLBiome SOUL_SAND_VALLEY_BIOME = registerNetherBiome(getFromRegistry(Biomes.SOUL_SAND_VALLEY));
+ public static final BCLBiome BASALT_DELTAS_BIOME = registerNetherBiome(getFromRegistry(Biomes.BASALT_DELTAS));
+
+ public static final BCLBiome THE_END = registerEndLandBiome(getFromRegistry(Biomes.THE_END));
+ public static final BCLBiome END_MIDLANDS = registerSubBiome(THE_END, getFromRegistry(Biomes.END_MIDLANDS), 0.5F);
+ public static final BCLBiome END_HIGHLANDS = registerSubBiome(THE_END, getFromRegistry(Biomes.END_HIGHLANDS), 0.5F);
+
+ public static final BCLBiome END_BARRENS = registerEndVoidBiome(getFromRegistry(new ResourceLocation("end_barrens")));
+ public static final BCLBiome SMALL_END_ISLANDS = registerEndVoidBiome(getFromRegistry(new ResourceLocation("small_end_islands")));
+
+ /**
+ * Initialize registry for current server.
+ * @param biomeRegistry - {@link Registry} for {@link Biome}.
+ */
+ public static void initRegistry(Registry biomeRegistry) {
+ BiomeAPI.biomeRegistry = biomeRegistry;
+ CLIENT.clear();
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerBiome(BCLBiome biome) {
+ if (BuiltinRegistries.BIOME.get(biome.getID()) == null) {
+ Registry.register(BuiltinRegistries.BIOME, biome.getID(), biome.getBiome());
+ }
+ ID_MAP.put(biome.getID(), biome);
+ return biome;
+ }
+
+ public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome) {
+ registerBiome(subBiome);
+ parent.addSubBiome(subBiome);
+ return subBiome;
+ }
+
+ public static BCLBiome registerSubBiome(BCLBiome parent, Biome biome, float genChance) {
+ BCLBiome subBiome = new BCLBiome(biome).setGenChance(genChance);
+ return registerSubBiome(parent, subBiome);
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API.
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerNetherBiome(BCLBiome biome) {
+ registerBiome(biome);
+ NETHER_BIOME_PICKER.addBiome(biome);
+ Random random = new Random(biome.getID().hashCode());
+
+ //TODO: 1.18 Check parameters, depth was previously called altitude
+ //temperature, humidity, continentalness, erosion, depth, weirdness, offset
+ Climate.ParameterPoint parameters = Climate.parameters(
+ MHelper.randRange(-1.5F, 1.5F, random),
+ MHelper.randRange(-1.5F, 1.5F, random),
+ MHelper.randRange(-1.5F, 1.5F, random), //new in 1.18
+ MHelper.randRange(-1.5F, 1.5F, random), //new in 1.18
+ MHelper.randRange(-1.5F, 1.5F, random),
+ MHelper.randRange(-1.5F, 1.5F, random),
+ random.nextFloat()
+ );
+ ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get();
+ NetherBiomeData.addNetherBiome(key, parameters);
+ return biome;
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API.
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerNetherBiome(Biome biome) {
+ BCLBiome bclBiome = new BCLBiome(biome);
+ configureBiome(bclBiome, 1.0F, 1.0F);
+ NETHER_BIOME_PICKER.addBiome(bclBiome);
+ registerBiome(bclBiome);
+ return bclBiome;
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerEndLandBiome(BCLBiome biome) {
+ registerBiome(biome);
+ configureBiome(biome, 1.0F, 1.0F);
+ END_LAND_BIOME_PICKER.addBiome(biome);
+ float weight = biome.getGenChance();
+ ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get();
+ TheEndBiomeData.addEndBiomeReplacement(Biomes.END_HIGHLANDS, key, weight);
+ TheEndBiomeData.addEndBiomeReplacement(Biomes.END_MIDLANDS, key, weight);
+ return biome;
+ }
+
+ /**
+ * Register {@link BCLBiome} wrapper for {@link Biome}.
+ * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerEndLandBiome(Biome biome) {
+ BCLBiome bclBiome = new BCLBiome(biome);
+ configureBiome(bclBiome, 1.0F, 1.0F);
+ END_LAND_BIOME_PICKER.addBiome(bclBiome);
+ registerBiome(bclBiome);
+ return bclBiome;
+ }
+
+ /**
+ * Register {@link BCLBiome} wrapper for {@link Biome}.
+ * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
+ * @param biome {@link BCLBiome};
+ * @param genChance float generation chance.
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerEndLandBiome(Biome biome, float genChance) {
+ BCLBiome bclBiome = new BCLBiome(biome);
+ configureBiome(bclBiome, genChance, 1.0F);
+ END_LAND_BIOME_PICKER.addBiome(bclBiome);
+ registerBiome(bclBiome);
+ return bclBiome;
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerEndVoidBiome(BCLBiome biome) {
+ registerBiome(biome);
+ configureBiome(biome, 1.0F, 1.0F);
+ END_VOID_BIOME_PICKER.addBiome(biome);
+ float weight = biome.getGenChance();
+ ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get();
+ TheEndBiomeData.addEndBiomeReplacement(Biomes.SMALL_END_ISLANDS, key, weight);
+ return biome;
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
+ * @param biome {@link BCLBiome}
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerEndVoidBiome(Biome biome) {
+ BCLBiome bclBiome = new BCLBiome(biome);
+ configureBiome(bclBiome, 1.0F, 1.0F);
+ END_VOID_BIOME_PICKER.addBiome(bclBiome);
+ registerBiome(bclBiome);
+ return bclBiome;
+ }
+
+ /**
+ * Register {@link BCLBiome} instance and its {@link Biome} if necessary.
+ * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
+ * @param biome {@link BCLBiome}.
+ * @param genChance float generation chance.
+ * @return {@link BCLBiome}
+ */
+ public static BCLBiome registerEndVoidBiome(Biome biome, float genChance) {
+ ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome).get();
+ BCLBiome bclBiome = new BCLBiome(biome);
+ configureBiome(bclBiome, genChance, 1.0F);
+ END_VOID_BIOME_PICKER.addBiome(bclBiome);
+ registerBiome(bclBiome);
+ return bclBiome;
+ }
+
+ /**
+ * Get {@link BCLBiome} from {@link Biome} instance on server. Used to convert world biomes to BCLBiomes.
+ * @param biome - {@link Biome} from world.
+ * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
+ */
+ public static BCLBiome getFromBiome(Biome biome) {
+ if (biomeRegistry == null) {
+ return EMPTY_BIOME;
+ }
+ return ID_MAP.getOrDefault(biomeRegistry.getKey(biome), EMPTY_BIOME);
+ }
+
+ /**
+ * Get {@link BCLBiome} from biome on client. Used in fog rendering.
+ * @param biome - {@link Biome} from client world.
+ * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
+ */
+ @Environment(EnvType.CLIENT)
+ public static BCLBiome getRenderBiome(Biome biome) {
+ BCLBiome endBiome = CLIENT.get(biome);
+ if (endBiome == null) {
+ Minecraft minecraft = Minecraft.getInstance();
+ ResourceLocation id = minecraft.level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getKey(biome);
+ endBiome = id == null ? EMPTY_BIOME : ID_MAP.getOrDefault(id, EMPTY_BIOME);
+ CLIENT.put(biome, endBiome);
+ }
+ return endBiome;
+ }
+
+ /**
+ * Get biome {@link ResourceKey} from given {@link Biome}.
+ * @param biome - {@link Biome} from server world.
+ * @return biome {@link ResourceKey} or {@code null}.
+ */
+ @Nullable
+ public static ResourceKey getBiomeKey(Biome biome) {
+ ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome).orElse(null);
+ return key != null ? key : biomeRegistry != null ? biomeRegistry.getResourceKey(biome).orElse(null) : null;
+ }
+
+ /**
+ * Get biome {@link ResourceLocation} from given {@link Biome}.
+ * @param biome - {@link Biome} from server world.
+ * @return biome {@link ResourceLocation}.
+ */
+ public static ResourceLocation getBiomeID(Biome biome) {
+ ResourceLocation id = BuiltinRegistries.BIOME.getKey(biome);
+ if (id == null && biomeRegistry != null) {
+ id = biomeRegistry.getKey(biome);
+ }
+ return id == null ? EMPTY_BIOME.getID() : id;
+ }
+
+ /**
+ * Get {@link BCLBiome} from given {@link ResourceLocation}.
+ * @param biomeID - biome {@link ResourceLocation}.
+ * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
+ */
+ public static BCLBiome getBiome(ResourceLocation biomeID) {
+ return ID_MAP.getOrDefault(biomeID, EMPTY_BIOME);
+ }
+
+ /**
+ * Check if biome with {@link ResourceLocation} exists in API registry.
+ * @param biomeID - biome {@link ResourceLocation}.
+ * @return {@code true} if biome exists in API registry and {@code false} if not.
+ */
+ public static boolean hasBiome(ResourceLocation biomeID) {
+ return ID_MAP.containsKey(biomeID);
+ }
+
+ /**
+ * Load biomes from Fabric API. For internal usage only.
+ */
+ public static void loadFabricAPIBiomes() {
+ FabricBiomesData.NETHER_BIOMES.forEach((key) -> {
+ if (!hasBiome(key.location())) {
+ registerNetherBiome(BuiltinRegistries.BIOME.get(key.location()));
+ }
+ });
+
+ FabricBiomesData.END_LAND_BIOMES.forEach((key, weight) -> {
+ if (!hasBiome(key.location())) {
+ registerEndLandBiome(BuiltinRegistries.BIOME.get(key.location()), weight);
+ }
+ });
+
+ FabricBiomesData.END_VOID_BIOMES.forEach((key, weight) -> {
+ if (!hasBiome(key.location())) {
+ registerEndVoidBiome(BuiltinRegistries.BIOME.get(key.location()), weight);
+ }
+ });
+ }
+
+ @Nullable
+ public static Biome getFromRegistry(ResourceLocation key) {
+ return BuiltinRegistries.BIOME.get(key);
+ }
+
+ @Nullable
+ public static Biome getFromRegistry(ResourceKey key) {
+ return BuiltinRegistries.BIOME.get(key);
+ }
+
+ public static boolean isDatapackBiome(ResourceLocation biomeID) {
+ return getFromRegistry(biomeID) == null;
+ }
+
+ public static boolean isNetherBiome(ResourceLocation biomeID) {
+ return pickerHasBiome(NETHER_BIOME_PICKER, biomeID);
+ }
+
+ public static boolean isEndBiome(ResourceLocation biomeID) {
+ return pickerHasBiome(END_LAND_BIOME_PICKER, biomeID) || pickerHasBiome(END_VOID_BIOME_PICKER, biomeID);
+ }
+
+ private static boolean pickerHasBiome(BiomePicker picker, ResourceLocation key) {
+ return picker.getBiomes().stream().filter(biome -> biome.getID().equals(key)).findFirst().isPresent();
+ }
+
+ /**
+ * Registers new biome modification for specified dimension. Will work both for mod and datapack biomes.
+ * @param dimensionID {@link ResourceLocation} dimension ID, example: Level.OVERWORLD or "minecraft:overworld".
+ * @param modification {@link BiConsumer} with {@link ResourceKey} biome ID and {@link Biome} parameters.
+ */
+ public static void registerBiomeModification(ResourceKey dimensionID, BiConsumer modification) {
+ List> modifications = MODIFICATIONS.get(dimensionID);
+ if (modifications == null) {
+ modifications = Lists.newArrayList();
+ MODIFICATIONS.put(dimensionID, modifications);
+ }
+ modifications.add(modification);
+ }
+
+ /**
+ * Registers new biome modification for the Overworld. Will work both for mod and datapack biomes.
+ * @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
+ */
+ public static void registerOverworldBiomeModification(BiConsumer modification) {
+ registerBiomeModification(Level.OVERWORLD, modification);
+ }
+
+ /**
+ * Registers new biome modification for the Nether. Will work both for mod and datapack biomes.
+ * @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
+ */
+ public static void registerNetherBiomeModification(BiConsumer modification) {
+ registerBiomeModification(Level.NETHER, modification);
+ }
+
+ /**
+ * Registers new biome modification for the End. Will work both for mod and datapack biomes.
+ * @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
+ */
+ public static void registerEndBiomeModification(BiConsumer modification) {
+ registerBiomeModification(Level.END, modification);
+ }
+
+ /**
+ * Will apply biome modifications to world, internal usage only.
+ * @param level
+ */
+ public static void applyModifications(ServerLevel level) {
+ BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource();
+ Set biomes = source.possibleBiomes();
+
+ NoiseGeneratorSettings generator = null;
+ if (level.dimension() == Level.NETHER) {
+ generator = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.NETHER);
+ }
+ else if (level.dimension() == Level.END) {
+ generator = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.END);
+ }
+
+ if (generator != null) {
+ List rules = getRuleSources(biomes, level.dimension());
+ SurfaceRuleProvider provider = SurfaceRuleProvider.class.cast(generator);
+ if (rules.size() > 0) {
+ rules.add(provider.getSurfaceRule());
+ provider.setSurfaceRule(SurfaceRules.sequence(rules.toArray(new SurfaceRules.RuleSource[rules.size()])));
+ }
+ else {
+ provider.setSurfaceRule(null);
+ }
+ }
+
+ List> modifications = MODIFICATIONS.get(level.dimension());
+ if (modifications == null) {
+ return;
+ }
+
+ biomes.forEach(biome -> {
+ ResourceLocation biomeID = getBiomeID(biome);
+ boolean modify = isDatapackBiome(biomeID);
+ if (biome != BuiltinRegistries.BIOME.get(biomeID)) {
+ modify = true;
+ }
+ else if (!modify && !MODIFIED_BIOMES.contains(biomeID)) {
+ MODIFIED_BIOMES.add(biomeID);
+ modify = true;
+ }
+ if (modify) {
+ modifications.forEach(consumer -> {
+ consumer.accept(biomeID, biome);
+ });
+ }
+ });
+ }
+
+ private static List getRuleSources(Set biomes, ResourceKey dimensionType) {
+ Set biomeIDs = biomes.stream().map(biome -> getBiomeID(biome)).collect(Collectors.toSet());
+ List rules = Lists.newArrayList();
+ SURFACE_RULES.forEach((biomeID, rule) -> {
+ if (biomeIDs.contains(biomeID)) {
+ rules.add(rule);
+ }
+ });
+
+ // Try handle biomes from other dimension, may work not as expected
+ // Will not work
+ /*Optional optional = biomes
+ .stream()
+ .filter(biome -> biome.getBiomeCategory() != BiomeCategory.THEEND && biome.getBiomeCategory() != BiomeCategory.NETHER)
+ .findAny();
+ if (optional.isPresent()) {
+ rules.add(SurfaceRuleData.overworld());
+ }
+
+ if (dimensionType == Level.NETHER) {
+ optional = biomes.stream().filter(biome -> biome.getBiomeCategory() != BiomeCategory.THEEND).findAny();
+ if (optional.isPresent()) {
+ rules.add(SurfaceRuleData.end());
+ }
+ }
+ else if (dimensionType == Level.END) {
+ optional = biomes.stream().filter(biome -> biome.getBiomeCategory() != BiomeCategory.NETHER).findAny();
+ if (optional.isPresent()) {
+ rules.add(SurfaceRuleData.end());
+ }
+ }*/
+
+ return rules;
+ }
+
+ /**
+ * Adds new features to existing biome.
+ * @param biome {@link Biome} to add features in.
+ * @param feature {@link ConfiguredFeature} to add.
+ *
+ */
+ public static void addBiomeFeature(Biome biome, BCLFeature feature) {
+ addBiomeFeature(biome, feature.getPlacedFeature(), feature.getDecoration());
+ }
+
+ /**
+ * Adds new features to existing biome.
+ * @param biome {@link Biome} to add features in.
+ * @param feature {@link ConfiguredFeature} to add.
+ * @param step a {@link Decoration} step for the feature.
+ */
+ public static void addBiomeFeature(Biome biome, PlacedFeature feature, Decoration step) {
+ BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.getGenerationSettings();
+ List>> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
+ Set set = CollectionsUtil.getMutable(accessor.bclib_getFeatureSet());
+ List> features = getFeaturesList(allFeatures, step);
+ features.add(() -> feature);
+ set.add(feature);
+ accessor.bclib_setFeatures(allFeatures);
+ accessor.bclib_setFeatureSet(set);
+ }
+
+ /**
+ * Adds new features to existing biome.
+ * @param biomeID {@link ResourceLocation} of the {@link Biome} to add features in.
+ * @param feature {@link ConfiguredFeature} to add.
+ * @param step a {@link Decoration} step for the feature.
+ */
+ private static void addBiomeFeature(ResourceLocation biomeID, PlacedFeature feature, Decoration step) {
+ addBiomeFeature(BuiltinRegistries.BIOME.get(biomeID), feature, step);
+ }
+
+ /**
+ * Adds new features to existing biome.
+ * @param biome {@link Biome} to add features in.
+ * @param features array of {@link BCLFeature} to add.
+ */
+ public static void addBiomeFeatures(Biome biome, BCLFeature... features) {
+ BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.getGenerationSettings();
+ List>> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
+ Set set = CollectionsUtil.getMutable(accessor.bclib_getFeatureSet());
+ for (BCLFeature feature: features) {
+ List> featureList = getFeaturesList(allFeatures, feature.getDecoration());
+ featureList.add(() -> feature.getPlacedFeature());
+ set.add(feature.getPlacedFeature());
+ }
+ accessor.bclib_setFeatures(allFeatures);
+ accessor.bclib_setFeatureSet(set);
+ }
+
+ /**
+ * Adds new structure feature to existing biome.
+ * @param biomeKey {@link ResourceKey} for the {@link Biome} to add structure feature in.
+ * @param structure {@link ConfiguredStructureFeature} to add.
+ */
+ public static void addBiomeStructure(ResourceKey biomeKey, ConfiguredStructureFeature structure) {
+ changeStructureStarts(structureMap -> {
+ Multimap, ResourceKey> configuredMap = structureMap.computeIfAbsent(structure.feature, k -> HashMultimap.create());
+
+ configuredMap.put(structure, biomeKey);
+ });
+ }
+
+ public static void addBiomeStructure(Biome biome, ConfiguredStructureFeature structure) {
+ changeStructureStarts(structureMap -> {
+ Multimap, ResourceKey> configuredMap = structureMap.computeIfAbsent(structure.feature, k -> HashMultimap.create());
+ var key = getBiomeKey(biome);
+ if (key!=null) {
+ configuredMap.put(structure, key);
+ } else {
+ BCLib.LOGGER.error("Unable to find Biome " + getBiomeID(biome));
+ }
+ });
+ }
+
+ private static void changeStructureStarts(Consumer