From 0e21693ec528f15757ac44c41ccfe0f212a77914 Mon Sep 17 00:00:00 2001 From: Matyrobbrt <65940752+Matyrobbrt@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:27:05 +0300 Subject: [PATCH 1/9] Document the waxables and oxidizables data maps (#147) --- docs/resources/server/datamaps/builtin.md | 58 ++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/docs/resources/server/datamaps/builtin.md b/docs/resources/server/datamaps/builtin.md index 47e71fda..b0a223a3 100644 --- a/docs/resources/server/datamaps/builtin.md +++ b/docs/resources/server/datamaps/builtin.md @@ -9,7 +9,9 @@ Allows configuring composter values, as a replacement for `ComposterBlock.COMPOS ```json5 { // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter - "chance": 1 + "chance": 1, + // Optional, defaults to false - whether farmer villagers can compost this item + "can_villager_compost": false } ``` @@ -98,6 +100,34 @@ Example: } ``` +## `neoforge:oxidizables` + +Allows configuring oxidation stages, as a replacement for `WeatheringCopper#NEXT_BY_BLOCK` (which will be ignored in 1.21.2). This data map is also used to build a reverse deoxidation map (for scraping with an axe). It is located at `neoforge/data_maps/block/oxidizables.json` and its objects have the following structure: + +```json5 +{ + // The block this block will turn into once oxidized + "next_oxidized_stage": "examplemod:oxidized_block" +} +``` + +:::note +Custom blocks must implement `WeatheringCopperFullBlock` or `WeatheringCopper` and call `changeOverTime` in `randomTick` to oxidize naturally. +::: + +Example: + +```json5 +{ + "values": { + "mymod:custom_copper": { + // Make a custom copper block oxidize into custom oxidized copper + "next_oxidized_stage": "mymod:custom_oxidized_copper" + } + } +} +``` + ## `neoforge:parrot_imitations` Allows configuring the sounds produced by parrots when they want to imitate a mob, as a replacement for `Parrot#MOB_SOUND_MAP` (which is now ignored). This data map is located at `neoforge/data_maps/entity_type/parrot_imitations.json` and its objects have the following structure: @@ -141,7 +171,7 @@ Example: "minecraft:armorer": { // Make armorers give the raid hero the armorer gift loot table "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" - }, + } } } ``` @@ -170,5 +200,29 @@ Example: } ``` +## `neoforge:waxables` + +Allows configuring the block a block will turn into when waxed (right clicked with a honeycomb), as a replacement for `HoneycombItem#WAXABLES` (which will be ignored in 1.21.2). This data map is also used to build a reverse dewaxing map (for scraping with an axe). It is located at `neoforge/data_maps/block/waxables.json` and its objects have the following structure: + +```json5 +{ + // The waxed variant of this block + "waxed": "minecraft:iron_block" +} +``` + +Example: + +```json5 +{ + "values": { + // Make gold blocks turn into iron blocks once waxed + "minecraft:gold_block": { + "waxed": "minecraft:iron_block" + } + } +} +``` + [datacomponent]: ../../../items/datacomponents.md [datamap]: index.md From 97e38a2847597fa244a0e0906d40e114bd4c051c Mon Sep 17 00:00:00 2001 From: Will Yelton Date: Tue, 13 Aug 2024 13:00:01 -0400 Subject: [PATCH 2/9] Fixes small typo in datamaps docs (#150) --- docs/resources/server/datamaps/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/server/datamaps/index.md b/docs/resources/server/datamaps/index.md index 4f7b160a..20b024d8 100644 --- a/docs/resources/server/datamaps/index.md +++ b/docs/resources/server/datamaps/index.md @@ -121,7 +121,7 @@ Like many other things, data maps are serialized and deserialized using [codecs] ```java public record ExampleData(float amount, float chance) { - public static final Codec CODEC = RecordCodecBuilder(instance -> instance.group( + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.FLOAT.fieldOf("amount").forGetter(ExampleData::amount), Codec.floatRange(0, 1).fieldOf("chance").forGetter(ExampleData::chance) ).apply(instance, ExampleData::new)); From 2363dbf33b58736dd6b95df121f5496e8c3db3bf Mon Sep 17 00:00:00 2001 From: ChampionAsh5357 Date: Wed, 14 Aug 2024 20:16:35 -0400 Subject: [PATCH 3/9] Update recipes -> recipe (#151) --- docs/resources/server/recipes/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/server/recipes/index.md b/docs/resources/server/recipes/index.md index 9e7b9811..359a6d46 100644 --- a/docs/resources/server/recipes/index.md +++ b/docs/resources/server/recipes/index.md @@ -2,7 +2,7 @@ Recipes are a way to transform a set of objects into other objects within a Minecraft world. Although Minecraft uses this system purely for item transformations, the system is built in a way that allows any kind of objects - blocks, entities, etc. - to be transformed. Almost all recipes use recipe data files; a "recipe" is assumed to be a data-driven recipe in this article unless explicitly stated otherwise. -Recipe data files are located at `data//recipes/.json`. For example, the recipe `minecraft:diamond_block` is located at `data/minecraft/recipes/diamond_block.json`. +Recipe data files are located at `data//recipe/.json`. For example, the recipe `minecraft:diamond_block` is located at `data/minecraft/recipe/diamond_block.json`. ## Terminology From 7f1f741912962f9e1be40d4076225a6862cc6a2f Mon Sep 17 00:00:00 2001 From: IchHabeHunger54 <52419336+IchHabeHunger54@users.noreply.github.com> Date: Sat, 17 Aug 2024 02:05:15 +0200 Subject: [PATCH 4/9] Fix incorrect reference to `Attribute` (#153) --- docs/items/mobeffects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/items/mobeffects.md b/docs/items/mobeffects.md index 826004ad..87cf3cf6 100644 --- a/docs/items/mobeffects.md +++ b/docs/items/mobeffects.md @@ -69,7 +69,7 @@ The `MobEffect` class also provides default functionality for adding attribute m ```java public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect(...) - .addAttributeModifier(Attribute.ATTACK_DAMAGE, ResourceLocation.fromNamespaceAndPath("examplemod", "effect.strength"), 2.0, AttributeModifier.Operation.ADD_VALUE) + .addAttributeModifier(Attributes.ATTACK_DAMAGE, ResourceLocation.fromNamespaceAndPath("examplemod", "effect.strength"), 2.0, AttributeModifier.Operation.ADD_VALUE) ); ``` From 1f8e3b33fc58e0dda67bbf43608a15c756e39723 Mon Sep 17 00:00:00 2001 From: Henry Loenwind Date: Mon, 26 Aug 2024 03:32:57 +0200 Subject: [PATCH 5/9] Fix "The Crafting Mechanic" example on "Recipes" (#155) --- docs/resources/server/recipes/index.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/resources/server/recipes/index.md b/docs/resources/server/recipes/index.md index 359a6d46..97322880 100644 --- a/docs/resources/server/recipes/index.md +++ b/docs/resources/server/recipes/index.md @@ -336,10 +336,9 @@ public static void useItemOnBlock(UseItemOnBlockEvent event) { UseOnContext context = event.getUseOnContext(); Level level = context.getLevel(); BlockPos pos = context.getClickedPos(); - BlockState blockState = context.getLevel().getBlockState(pos); + BlockState blockState = level.getBlockState(pos); ItemStack itemStack = context.getItemInHand(); - // If the level is not a server level, return. - if (level.isClientSide()) return; + RecipeManager recipes = level.getRecipeManager(); // Create an input and query the recipe. RightClickBlockInput input = new RightClickBlockInput(blockState, itemStack); Optional>> optional = recipes.getRecipeFor( @@ -354,14 +353,17 @@ public static void useItemOnBlock(UseItemOnBlockEvent event) { .orElse(ItemStack.EMPTY); // If there is a result, break the block and drop the result in the world. if (!result.isEmpty()) { - level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL); - ItemEntity entity = new ItemEntity(level, - // Center of pos. - pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, - result); - level.addFreshEntity(entity); + level.removeBlock(pos, false); + // If the level is not a server level, don't spawn the entity. + if (!level.isClientSide()) { + ItemEntity entity = new ItemEntity(level, + // Center of pos. + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + result); + level.addFreshEntity(entity); + } // Cancel the event to stop the interaction pipeline. - event.setCanceled(true); + event.cancelWithResult(ItemInteractionResult.sidedSuccess(level.isClientSide)); } } ``` From e8f838886c619e5c0a1de768c4e6e47a9471bb87 Mon Sep 17 00:00:00 2001 From: ChampionAsh5357 Date: Sun, 8 Sep 2024 08:55:09 -0400 Subject: [PATCH 6/9] Fix Network Changes on Payload Thread Handling (#156) Co-authored-by: Dennis C --- docs/networking/payload.md | 80 ++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/docs/networking/payload.md b/docs/networking/payload.md index 75534e74..e5e7076c 100644 --- a/docs/networking/payload.md +++ b/docs/networking/payload.md @@ -57,8 +57,8 @@ public static void register(final RegisterPayloadHandlersEvent event) { MyData.TYPE, MyData.STREAM_CODEC, new DirectionalPayloadHandler<>( - ClientPayloadHandler::handleData, - ServerPayloadHandler::handleData + ClientPayloadHandler::handleDataOnMain, + ServerPayloadHandler::handleDataOnMain ) ); } @@ -79,7 +79,72 @@ Now that we have registered the payload we need to implement a handler. For this ```java public class ClientPayloadHandler { - public static void handleData(final MyData data, final IPayloadContext context) { + public static void handleDataOnMain(final MyData data, final IPayloadContext context) { + // Do something with the data, on the main thread + blah(data.age()); + } +} +``` + +Here a couple of things are of note: + +- The handling method here gets the payload, and a contextual object. +- The handling method of the payload is, by default, invoked on the main thread. + + +If you need to do some computation that is resource intensive, then the work should be done on the network thread, instead of blocking the main thread. This is done by setting the `HandlerThread` of the `PayloadRegistrar` to `HandlerThread#NETWORK` via `PayloadRegistrar#executesOn` before registering the payload. + +```java +@SubscribeEvent +public static void register(final RegisterPayloadHandlersEvent event) { + final PayloadRegistrar registrar = event.registrar("1") + .executesOn(HandlerThread.NETWORK); // All subsequent payloads will register on the network thread + registrar.playBidirectional( + MyData.TYPE, + MyData.STREAM_CODEC, + new DirectionalPayloadHandler<>( + ClientPayloadHandler::handleDataOnNetwork, + ServerPayloadHandler::handleDataOnNetwork + ) + ); +} +``` + +:::note +All payloads registered after an `executesOn` call will retain the same thread execution location until `executesOn` is called again. + +```java +PayloadRegistrar registrar = event.registrar("1"); + +registrar.playBidirectional(...); // On the main thread +registrar.playBidirectional(...); // On the main thread + +// Configuration methods modify the state of the registrar +// by creating a new instance, so the change needs to be +/// updated by storing the result +registrar = registrar.executesOn(HandlerThread.NETWORK); + +registrar.playBidirectional(...); // On the network thread +registrar.playBidirectional(...); // On the network thread + +registrar = registrar.executesOn(HandlerThread.MAIN); + +registrar.playBidirectional(...); // On the main thread +registrar.playBidirectional(...); // On the main thread +``` +::: + +Here a couple of things are of note: + +- If you want to run code on the main game thread you can use `enqueueWork` to submit a task to the main thread. + - The method will return a `CompletableFuture` that will be completed on the main thread. + - Notice: A `CompletableFuture` is returned, this means that you can chain multiple tasks together, and handle exceptions in a single place. + - If you do not handle the exception in the `CompletableFuture` then it will be swallowed, **and you will not be notified of it**. + +```java +public class ClientPayloadHandler { + + public static void handleDataOnNetwork(final MyData data, final IPayloadContext context) { // Do something with the data, on the network thread blah(data.name()); @@ -96,15 +161,6 @@ public class ClientPayloadHandler { } ``` -Here a couple of things are of note: - -- The handling method here gets the payload, and a contextual object. -- The handler of the payload method is invoked on the networking thread, so it is important to do all the heavy work here, instead of blocking the main game thread. -- If you want to run code on the main game thread you can use `enqueueWork` to submit a task to the main thread. - - The method will return a `CompletableFuture` that will be completed on the main thread. - - Notice: A `CompletableFuture` is returned, this means that you can chain multiple tasks together, and handle exceptions in a single place. - - If you do not handle the exception in the `CompletableFuture` then it will be swallowed, **and you will not be notified of it**. - With your own payloads you can then use those to configure the client and server using [Configuration Tasks][configuration]. ## Sending Payloads From aa94a3643ffd59d0cc5265541dd12bae282bb2e6 Mon Sep 17 00:00:00 2001 From: ChampionAsh5357 Date: Tue, 17 Sep 2024 13:19:02 -0400 Subject: [PATCH 7/9] Update data components registrar method (#164) --- docs/datastorage/attachments.md | 2 +- docs/datastorage/nbt.md | 2 +- .../{datacomponents.md => datacomponents.mdx} | 62 +++++++++++++++++++ docs/items/index.md | 2 +- docs/items/tools.md | 2 +- docs/resources/server/datamaps/builtin.md | 2 +- .../server/loottables/lootfunctions.md | 2 +- 7 files changed, 68 insertions(+), 6 deletions(-) rename docs/items/{datacomponents.md => datacomponents.mdx} (85%) diff --git a/docs/datastorage/attachments.md b/docs/datastorage/attachments.md index 8fc59c4d..08be2052 100644 --- a/docs/datastorage/attachments.md +++ b/docs/datastorage/attachments.md @@ -114,5 +114,5 @@ NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { ``` [saveddata]: ./saveddata.md -[datacomponents]: ../items/datacomponents.md +[datacomponents]: ../items/datacomponents.mdx [network]: ../networking/index.md diff --git a/docs/datastorage/nbt.md b/docs/datastorage/nbt.md index 4532cf06..335d7308 100644 --- a/docs/datastorage/nbt.md +++ b/docs/datastorage/nbt.md @@ -101,5 +101,5 @@ NBT is used in a lot of places in Minecraft. Some of the most common examples in [blockentity]: ../blockentities/index.md [datapack]: ../resources/index.md#data -[datacomponents]: ../items/datacomponents.md +[datacomponents]: ../items/datacomponents.mdx [nbtwiki]: https://minecraft.wiki/w/NBT_format diff --git a/docs/items/datacomponents.md b/docs/items/datacomponents.mdx similarity index 85% rename from docs/items/datacomponents.md rename to docs/items/datacomponents.mdx index 79ea3a9f..cedda0e9 100644 --- a/docs/items/datacomponents.md +++ b/docs/items/datacomponents.mdx @@ -1,6 +1,9 @@ --- sidebar_position: 2 --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # Data Components Data components are key-value pairs within a map used to store data on an `ItemStack`. Each piece of data, such as firework explosions or tools, are stored as actual objects on the stack, making the values visible and operable without having to dynamically transform a general encoded instance (e.g., `CompoundTag`, `JsonElement`). @@ -65,6 +68,63 @@ Either `persistent` or `networkSynchronized` must be provided in the builder; ot `DataComponentType` are registry objects and must be [registered]. + + + +```java +// Using ExampleRecord(int, boolean) +// Only one Codec and/or StreamCodec should be used below +// Multiple are provided for an example + +// Basic codec +public static final Codec BASIC_CODEC = RecordCodecBuilder.create(instance -> + instance.group( + Codec.INT.fieldOf("value1").forGetter(ExampleRecord::value1), + Codec.BOOL.fieldOf("value2").forGetter(ExampleRecord::value2) + ).apply(instance, ExampleRecord::new) +); +public static final StreamCodec BASIC_STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ExampleRecord::value1, + ByteBufCodecs.BOOL, ExampleRecord::value2, + ExampleRecord::new +); + +// Unit stream codec if nothing should be sent across the network +public static final StreamCodec UNIT_STREAM_CODEC = StreamCodec.unit(new ExampleRecord(0, false)); + + +// In another class +// The specialized DeferredRegister.DataComponents simplifies data component registration and avoids some generic inference issues with the `DataComponentType.Builder` within a `Supplier` +public static final DeferredRegister.DataComponents REGISTRAR = DeferredRegister.createDataComponents(Registries.DATA_COMPONENT_TYPE, "examplemod"); + +public static final DeferredHolder, DataComponentType> BASIC_EXAMPLE = REGISTRAR.registerComponentType( + "basic", + builder -> builder + // The codec to read/write the data to disk + .persistent(BASIC_CODEC) + // The codec to read/write the data across the network + .networkSynchronized(BASIC_STREAM_CODEC) +); + +/// Component will not be saved to disk +public static final DeferredHolder, DataComponentType> TRANSIENT_EXAMPLE = REGISTRAR.registerComponentType( + "transient", + builder -> builder.networkSynchronized(BASIC_STREAM_CODEC) +); + +// No data will be synced across the network +public static final DeferredHolder, DataComponentType> NO_NETWORK_EXAMPLE = REGISTRAR.registerComponentType( + "no_network", + builder -> builder + .persistent(BASIC_CODEC) + // Note we use a unit stream codec here + .networkSynchronized(UNIT_STREAM_CODEC) +); +``` + + + + ```java // Using ExampleRecord(int, boolean) // Only one Codec and/or StreamCodec should be used below @@ -115,6 +175,8 @@ public static final DeferredHolder, DataComponentType + ## The Component Map diff --git a/docs/items/index.md b/docs/items/index.md index 33460b63..c0fa0ab5 100644 --- a/docs/items/index.md +++ b/docs/items/index.md @@ -214,7 +214,7 @@ It is also possible to implement `ItemLike` on your custom objects. Simply overr [blockstates]: ../blocks/states.md [breaking]: ../blocks/index.md#breaking-a-block [creativetabs]: #creative-tabs -[datacomponents]: ./datacomponents.md +[datacomponents]: ./datacomponents.mdx [datagen]: ../resources/index.md#data-generation [food]: #food [hunger]: https://minecraft.wiki/w/Hunger#Mechanics diff --git a/docs/items/tools.md b/docs/items/tools.md index 14dd05e1..3f489132 100644 --- a/docs/items/tools.md +++ b/docs/items/tools.md @@ -233,7 +233,7 @@ public static final Supplier COPPER_BOOTS = ITEMS.register("copper_bo When creating your armor texture, it is a good idea to work on top of the vanilla armor texture to see which part goes where. [block]: ../blocks/index.md -[datacomponents]: ./datacomponents.md +[datacomponents]: ./datacomponents.mdx [item]: index.md [itemability]: #itemabilitys [tags]: ../resources/server/tags.md diff --git a/docs/resources/server/datamaps/builtin.md b/docs/resources/server/datamaps/builtin.md index b0a223a3..87d1e24b 100644 --- a/docs/resources/server/datamaps/builtin.md +++ b/docs/resources/server/datamaps/builtin.md @@ -224,5 +224,5 @@ Example: } ``` -[datacomponent]: ../../../items/datacomponents.md +[datacomponent]: ../../../items/datacomponents.mdx [datamap]: index.md diff --git a/docs/resources/server/loottables/lootfunctions.md b/docs/resources/server/loottables/lootfunctions.md index 9f94ca2a..8b519056 100644 --- a/docs/resources/server/loottables/lootfunctions.md +++ b/docs/resources/server/loottables/lootfunctions.md @@ -848,7 +848,7 @@ During datagen, call `SequenceFunction#of` with the other functions to construct [component]: ../../client/i18n.md#components [conditions]: lootconditions [custom]: custom.md#custom-loot-functions -[datacomponent]: ../../../items/datacomponents.md +[datacomponent]: ../../../items/datacomponents.mdx [entitytarget]: index.md#entity-targets [entry]: index.md#loot-entry [itemmodifiers]: https://minecraft.wiki/w/Item_modifier#JSON_format From 664c431a94e5c45c6fdc5d8663d331491146234e Mon Sep 17 00:00:00 2001 From: TelepathicGrunt <40846040+TelepathicGrunt@users.noreply.github.com> Date: Thu, 19 Sep 2024 04:20:48 -0400 Subject: [PATCH 8/9] Biome Modifier documentation (#161) Co-authored-by: ChampionAsh5357 Co-authored-by: IchHabeHunger54 <52419336+IchHabeHunger54@users.noreply.github.com> --- docs/advanced/_category_.json | 2 +- docs/legacy/_category_.json | 2 +- docs/misc/_category_.json | 2 +- docs/networking/_category_.json | 2 +- docs/worldgen/_category_.json | 4 + docs/worldgen/biomemodifier.md | 702 ++++++++++++++++++++++++++++++++ 6 files changed, 710 insertions(+), 4 deletions(-) create mode 100644 docs/worldgen/_category_.json create mode 100644 docs/worldgen/biomemodifier.md diff --git a/docs/advanced/_category_.json b/docs/advanced/_category_.json index 1e089c9b..0d103c92 100644 --- a/docs/advanced/_category_.json +++ b/docs/advanced/_category_.json @@ -1,4 +1,4 @@ { "label": "Advanced Topics", - "position": 11 + "position": 12 } \ No newline at end of file diff --git a/docs/legacy/_category_.json b/docs/legacy/_category_.json index ba1e3d4b..57032bfb 100644 --- a/docs/legacy/_category_.json +++ b/docs/legacy/_category_.json @@ -1,4 +1,4 @@ { "label": "Legacy", - "position": 13 + "position": 14 } \ No newline at end of file diff --git a/docs/misc/_category_.json b/docs/misc/_category_.json index d0f56eb4..3f9e25c7 100644 --- a/docs/misc/_category_.json +++ b/docs/misc/_category_.json @@ -1,4 +1,4 @@ { "label": "Miscellaneous", - "position": 12 + "position": 13 } \ No newline at end of file diff --git a/docs/networking/_category_.json b/docs/networking/_category_.json index 50b8e9da..366f94e3 100644 --- a/docs/networking/_category_.json +++ b/docs/networking/_category_.json @@ -1,4 +1,4 @@ { "label": "Networking", - "position": 10 + "position": 11 } \ No newline at end of file diff --git a/docs/worldgen/_category_.json b/docs/worldgen/_category_.json new file mode 100644 index 00000000..7b025865 --- /dev/null +++ b/docs/worldgen/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Worldgen", + "position": 10 +} \ No newline at end of file diff --git a/docs/worldgen/biomemodifier.md b/docs/worldgen/biomemodifier.md new file mode 100644 index 00000000..f4ad33cb --- /dev/null +++ b/docs/worldgen/biomemodifier.md @@ -0,0 +1,702 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Biome Modifiers + +Biome Modifiers are a data-driven system that allows for changing many aspects of a biome, including the ability to inject or remove PlacedFeatures, add or remove mob spawns, change the climate, and adjust foliage and water color. NeoForge provides several default biome modifiers that cover the majority of use cases for both players and modders. + +### Recommended Section To Read: + +- Players or pack developers: + - [Applying Biome Modifiers](#Applying-Biome-Modifiers) + - [Built-in Neoforge Biome Modifiers](#Built-in-Biome-Modifiers) + + +- Modders doing simple additions or removal biome modifications: + - [Applying Biome Modifiers](#Applying-Biome-Modifiers) + - [Built-in Neoforge Biome Modifiers](#Built-in-Biome-Modifiers) + - [Datagenning Biome Modifiers](#Datagenning-Biome-Modifiers) + + +- Modders who want to do custom or complex biome modifications: + - [Applying Biome Modifiers](#Applying-Biome-Modifiers) + - [Creating Custom Biome Modifiers](#Creating-Custom-Biome-Modifiers) + - [Datagenning Biome Modifiers](#Datagenning-Biome-Modifiers) + + +## Applying Biome Modifiers + +To have NeoForge load a biome modifier JSON file into the game, the file will need to be under `data//neoforge/biome_modifier/.json` folder in the mod's resources, or in a [Datapack][datapacks]. Then, once NeoForge loads the biome modifier, it will read its instructions and apply the described modifications to all target biomes when the world is loaded up. Pre-existing biome modifiers from mods can be overridden by datapacks having a new JSON file at the exact same location and name. + +The JSON file can be created by hand following the examples in the '[Built-in NeoForge Biome Modifiers](#Built-in-Biome-Modifiers)' section or be datagenned as shown in the '[Datagenning Biome Modifiers](#Datagenning-Biome-Modifiers)' section. + +## Built-in Biome Modifiers + +These biome modifiers are registered by NeoForge for anyone to use. + +### None + +This biome modifier has no operation and will do no modification. Pack makers and players can use this in a datapack to disable mods' biome modifiers by overriding their biome modifier JSONs with the JSON below. + + + + +```json5 +{ + "type": "neoforge:none" +} +``` + + + + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey NO_OP_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "no_op_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Register the biome modifiers. + bootstrap.register(NO_OP_EXAMPLE, NoneBiomeModifier.INSTANCE); +}); +``` + + + + +### Add Features + +This biome modifier type adds `PlacedFeature`s (such as trees or ores) to biomes so that they can spawn during world generation. The modifier takes in the biome id or tag of the biomes the features are added to, a `PlacedFeature` id or tag to add to the selected biomes, and the [`GenerationStep.Decoration`](#Available-Values-for-Decoration-Steps) the features will be generated within. + + + + +```json5 +{ + "type": "neoforge:add_features", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:your_biome_tag", + // Can either be a placed feature id, such as "examplemod:add_features_example", + // or a list of placed feature ids, such as ["examplemod:add_features_example", minecraft:ice_spike", ...], + // or a placed feature tag, such as "#examplemod:placed_feature_tag". + "features": "namespace:your_feature", + // See the GenerationStep.Decoration enum in code for a list of valid enum names. + // The decoration step section further down also has the list of values for reference. + "step": "underground_ores" +} +``` + + + + +```java +// Assume we have some PlacedFeature named EXAMPLE_PLACED_FEATURE. +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey ADD_FEATURES_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "add_features_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter placedFeatures = bootstrap.lookup(Registries.PLACED_FEATURE); + + // Register the biome modifiers. + bootstrap.register(ADD_FEATURES_EXAMPLE, + new AddFeaturesBiomeModifier( + // The biome(s) to generate within + HolderSet.direct(biomes.getOrThrow(Biomes.PLAINS)), + // The feature(s) to generate within the biomes + HolderSet.direct(placedFeatures.getOrThrow(EXAMPLE_PLACED_FEATURE)), + // The generation step + GenerationStep.Decoration.LOCAL_MODIFICATIONS + ) + ); +}) +``` + + + + + +:::warning +Care should be taken when adding vanilla `PlacedFeature`s to biomes, as doing so may cause what is known as a feature cycle violation (two biomes having the same two features in their feature lists, but in different orders within the same `GenerationStep`), leading to a crash. For similar reasons, you should not use the same `PlacedFeature` in more than one biome modifier. + +Vanilla `PlacedFeature`s can be referenced in biome JSONs or added via biome modifiers, but should not be used in both. If you still need to add them this way, making a copy of the vanilla `PlacedFeature` under your own namespace is the easiest solution to avoid these problems. +::: + +### Remove Features + +This biome modifier type removes features (such as trees or ores) from biomes so that they will no longer spawn during world generation. The modifier takes in the biome id or tag of the biomes the features are removed from, a `PlacedFeature` id or tag to remove from the selected biomes, and the [`GenerationStep.Decoration`](#Available-Values-for-Decoration-Steps)s that the features will be removed from. + + + + +```json5 +{ + "type": "neoforge:remove_features", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:your_biome_tag", + // Can either be a placed feature id, such as "examplemod:add_features_example", + // or a list of placed feature ids, such as ["examplemod:add_features_example", "minecraft:ice_spike", ...], + // or a placed feature tag, such as "#examplemod:placed_feature_tag". + "features": "namespace:problematic_feature", + // Optional field specifying a GenerationStep, or a list of GenerationSteps, to remove features from. + // If omitted, defaults to all GenerationSteps. + // See the GenerationStep.Decoration enum in code for a list of valid enum names. + // The decoration step section further down also has the list of values for reference. + "steps": ["underground_ores", "underground_decoration"] +} +``` + + + + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey REMOVE_FEATURES_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "remove_features_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter placedFeatures = bootstrap.lookup(Registries.PLACED_FEATURE); + + // Register the biome modifiers. + bootstrap.register(REMOVE_FEATURES_EXAMPLE, + new RemoveFeaturesBiomeModifier( + // The biome(s) to remove from + biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), + // The feature(s) to remove from the biomes + HolderSet.direct(placedFeatures.getOrThrow(OrePlacements.ORE_DIAMOND)), + // The generation steps to remove from + Set.of( + GenerationStep.Decoration.LOCAL_MODIFICATIONS, + GenerationStep.Decoration.UNDERGROUND_ORES + ) + ) + ); +}); +``` + + + + + +### Add Spawns + +This biome modifier type adds entity spawns to biomes. The modifier takes in the biome id or tag of the biomes the entity spawns are added to, and the `SpawnerData` of the entities to add. Each `SpawnerData` contains the entity id, the spawn weight, and the minimum/maximum number of entities to spawn at a given time. + +:::note +If you are a modder adding a new entity, make sure the entity has a spawn restriction registered to `RegisterSpawnPlacementsEvent`. Spawn restrictions are used to make entities spawn on surfaces or in water safely. If you do not register a spawn restriction, your entity could spawn in mid-air, fall and die. +::: + + + + +```json5 +{ + "type": "neoforge:add_spawns", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can be either a single object or a list of objects. + "spawners": [ + { + "type": "namespace:entity_type", // The id of the entity type to spawn + "weight": 100, // int, spawn weight + "minCount": 1, // int, minimum group size + "maxCount": 4 // int, maximum group size + }, + { + "type": "minecraft:ghast", + "weight": 1, + "minCount": 5, + "maxCount": 10 + } + ] +} +``` + + + + +```java +// Assume we have some EntityType named EXAMPLE_ENTITY. +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey ADD_SPAWNS_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "add_spawns_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + + // Register the biome modifiers. + bootstrap.register(ADD_SPAWNS_EXAMPLE, + new AddSpawnsBiomeModifier( + // The biome(s) to spawn the mobs within + HolderSet.direct(biomes.getOrThrow(Biomes.PLAINS)), + // The spawners of the entities to add + List.of( + new SpawnerData(EXAMPLE_ENTITY, 100, 1, 4), + new SpawnerData(EntityType.GHAST, 1, 5, 10) + ) + ) + ); +}); +``` + + + + + +### Remove Spawns + +This biome modifier type removes entity spawns from biomes. The modifier takes in the biome id or tag of the biomes the entity spawns are removed from, and the `EntityType` id or tag of the entities to remove. + + + + +```json5 +{ + "type": "neoforge:remove_spawns", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can either be an entity type id, such as "minecraft:ghast", + // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], + // or an entity type tag, such as "#minecraft:skeletons". + "entity_types": "#namespace:entitytype_tag" +} +``` + + + + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey REMOVE_SPAWNS_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "remove_spawns_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter> entities = bootstrap.lookup(Registries.ENTITY_TYPE); + + // Register the biome modifiers. + bootstrap.register(REMOVE_SPAWNS_EXAMPLE, + new RemoveSpawnsBiomeModifier( + // The biome(s) to remove the spawns from + biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), + // The entities to remove spawns for + entities.getOrThrow(EntityTypeTags.SKELETONS) + ) + ); +}); +``` + + + + + +### Add Spawn Costs + +Allows for adding new spawn costs to biomes. Spawn costs are a newer way of making mobs spawn spread out in a biome to reduce clustering. It works by having the entities give off a `charge` that surrounds them and adds up with other entities' `charge`. When spawning a new entity, the spawning algorithm looks for a spot where the total `charge` field at the location multiplied by the spawning entity's `charge` value is less than the spawning entity's `energy_budget`. This is an advanced way of spawning mobs, so it is a good idea to reference the Soul Sand Valley biome (which is the most prominent user of this system) for existing values to borrow. + +The modifier takes in the biome id or tag of the biomes the spawn costs are added to, the `EntityType` id or tag of the entity types to add spawn costs for, and the `MobSpawnSettings.MobSpawnCost` of the entity. The `MobSpawnCost` contains the energy budget, which indicates the maximum number of entities that can spawn in a location based on the charge provided for each entity spawned. + +:::note +If you are a modder adding a new entity, make sure the entity has a spawn restriction registered to `RegisterSpawnPlacementsEvent`. +::: + + + + +```json5 +{ + "type": "neoforge:add_spawn_costs", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can either be an entity type id, such as "minecraft:ghast", + // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], + // or an entity type tag, such as "#minecraft:skeletons". + "entity_types": "#minecraft:skeletons", + "spawn_cost": { + // The energy budget + "energy_budget": 1.0, + // The amount of charge each entity takes up from the budget + "charge": 0.1 + } +} +``` + + + + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey ADD_SPAWN_COSTS_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "add_spawn_costs_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter> entities = bootstrap.lookup(Registries.ENTITY_TYPE); + + // Register the biome modifiers. + bootstrap.register(ADD_SPAWN_COSTS_EXAMPLE, + new AddSpawnCostsBiomeModifier( + // The biome(s) to add the spawn costs to + biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), + // The entities to add the spawn costs for + entities.getOrThrow(EntityTypeTags.SKELETONS), + new MobSpawnSettings.MobSpawnCost( + 1.0, // The energy budget + 0.1 // The amount of charge each entity takes up from the budget + ) + ) + ); +}); +``` + + + + + +### Remove Spawn Costs + +Allows for removing a spawn cost from a biome. Spawn costs are a newer way of making mobs spawn spread out in a biome to reduce clustering. The modifier takes in the biome id or tag of the biomes the spawn costs are removed from, and the `EntityType` id or tag of the entities to remove the spawn cost for. + + + + +```json5 +{ + "type": "neoforge:remove_spawn_costs", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can either be an entity type id, such as "minecraft:ghast", + // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], + // or an entity type tag, such as "#minecraft:skeletons". + "entity_types": "#minecraft:skeletons" +} +``` + + + + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey REMOVE_SPAWN_COSTS_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "remove_spawn_costs_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter> entities = bootstrap.lookup(Registries.ENTITY_TYPE); + + // Register the biome modifiers. + bootstrap.register(REMOVE_SPAWN_COSTS_EXAMPLE, + new RemoveSpawnCostsBiomeModifier( + // The biome(s) to remove the spawn costs from + biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), + // The entities to remove spawn costs for + entities.getOrThrow(EntityTypeTags.SKELETONS) + ) + ); +}); +``` + + + + + +### Add Legacy Carvers + +This biome modifier type allows adding carver caves and ravines to biomes. These are what was used for cave generation before the Caves and Cliffs update. It CANNOT add noise caves to biomes, because noise caves are a part of certain noise-based chunk generator systems and not actually tied to biomes. + + + + +```json5 +{ + "type": "neoforge:add_carvers", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "minecraft:plains", + // Can either be a carver id, such as "examplemod:add_carvers_example", + // or a list of carver ids, such as ["examplemod:add_carvers_example", "minecraft:canyon", ...], + // or a carver tag, such as "#examplemod:configured_carver_tag". + "carvers": "examplemod:add_carvers_example", + // See GenerationStep.Carving in code for a list of valid enum names. + // Only "air" and "liquid" are available. + "step": "air" +} +``` + + + + +```java +// Assume we have some ConfiguredWorldCarver named EXAMPLE_CARVER. +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey ADD_CARVERS_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "add_carvers_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter> carvers = bootstrap.lookup(Registries.CONFIGURED_CARVER); + + // Register the biome modifiers. + bootstrap.register(ADD_CARVERS_EXAMPLE, + new AddCarversBiomeModifier( + // The biome(s) to generate within + HolderSet.direct(biomes.getOrThrow(Biomes.PLAINS)), + // The carver(s) to generate within the biomes + HolderSet.direct(carvers.getOrThrow(EXAMPLE_CARVER)), + // The generation step + GenerationStep.Carving.AIR + ) + ); +}); +``` + + + + +### Removing Legacy Carvers + +This biome modifier type allows removing carver caves and ravines from biomes. These are what was used for cave generation before the Caves and Cliffs update. It CANNOT remove noise caves from biomes, because noise caves are baked into the dimension's noise settings system and not actually tied to biomes. + + + + +```json5 +{ + "type": "neoforge:remove_carvers", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "minecraft:plains", + // Can either be a carver id, such as "examplemod:add_carvers_example", + // or a list of carver ids, such as ["examplemod:add_carvers_example", "minecraft:canyon", ...], + // or a carver tag, such as "#examplemod:configured_carver_tag". + "carvers": "examplemod:add_carvers_example", + // Can either be a single generation step, such as "air", + // or a list of generation steps, such as ["air", "liquid"]. + // See GenerationStep.Carving for a list of valid enum names. + // Only "air" and "liquid" are available. + "steps": [ + "air", + "liquid" + ] +} +``` + + + + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey REMOVE_CARVERS_EXAMPLE = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "remove_carvers_example") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + HolderGetter> carvers = bootstrap.lookup(Registries.CONFIGURED_CARVER); + + // Register the biome modifiers. + bootstrap.register(REMOVE_CARVERS_EXAMPLE, + new AddFeaturesBiomeModifier( + // The biome(s) to remove from + biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), + // The carver(s) to remove from the biomes + HolderSet.direct(carvers.getOrThrow(Carvers.CAVE)), + // The generation steps to remove from + Set.of( + GenerationStep.Carving.AIR, + GenerationStep.Carving.LIQUID + ) + ) + ); +}); +``` + + + + +### Available Values for Decoration Steps + +The `step` or `steps` fields in many of the aforementioned JSONs are referring to the `GenerationStep.Decoration` enum. This enum has the steps listed out in the following order, which is the same order that the game uses for generating during worldgen. Try to put features in the step that makes the most sense for them. + +| Step | Description | +|:------------------------:|:----------------------------------------------------------------------------------------| +| `raw_generation` | First to run. This is used for special terrain-like features such as Small End Islands. | +| `lakes` | Dedicated to spawning pond-like feature such as Lava Lakes. | +| `local_modifications` | For modifications to terrain such as Geodes, Icebergs, Boulders, or Dripstone. | +| `underground_structures` | Used for small underground structure-like features such as Dungeons or Fossils. | +| `surface_structures` | For small surface only structure-like features such as Desert Wells. | +| `strongholds` | Dedicated for Stronghold structures. No feature is added here in unmodified Minecraft. | +| `underground_ores` | The step for all Ores and Veins to be added to. This includes Gold, Dirt, Granite, etc. | +| `underground_decoration` | Used typically for decorating caves. Dripstone Cluster and Sculk Vein are here. | +| `fluid_springs` | The small Lavafalls and Waterfalls come from features in this stage. | +| `vegetal_decoration` | Nearly all plants (flowers, trees, vines, and more) are added to this stage. | +| `top_layer_modification` | Last to run. Used for placing Snow and Ice on the surface of cold biomes. | + + +## Creating Custom Biome Modifiers + +### The `BiomeModifier` Implementation + +Under the hood, Biome Modifiers are made up of three parts: + +- The [datapack registered][datareg] `BiomeModifier` used to modify the biome builder. +- The [statically registered][staticreg] `MapCodec` that encodes and decodes the modifiers. +- The JSON that constructs the `BiomeModifier`, using the registered id of the `MapCodec` as the indexable type. + +A `BiomeModifier` contains two methods: `#modify` and `#codec`. `modify` takes in a `Holder` of the current `Biome`, the current `BiomeModifier.Phase`, and the builder of the biome to modify. Every `BiomeModifier` is called once per `Phase` to organize when certain modifications to the biome should occur: + +| Phase | Description | +|:-------------------:|:-------------------------------------------------------------------------| +| `BEFORE_EVERYTHING` | A catch-all for everything that needs to run before the standard phases. | +| `ADD` | Adding features, mob spawns, etc. | +| `REMOVE` | Removing features, mob spawns, etc. | +| `MODIFY` | Modifying single values (e.g., climate, colors). | +| `AFTER_EVERYTHING` | A catch-all for everything that needs to run after the standard phases. | + +All `BiomeModifier`s contain a `type` key that references the id of the `MapCodec` used for the `BiomeModifier`. The `codec` takes in the `MapCodec` that encodes and decodes the modifiers. This `MapCodec` is [statically registered][staticreg], with its id used as the `type` of the `BiomeModifier`. + +```java +public record ExampleBiomeModifier(HolderSet biomes, int value) implements BiomeModifier { + + @Override + public void modify(Holder biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) { + if (phase == /* Pick the phase that best matches what your want to modify */) { + // Modify the 'builder', checking any information about the biome itself + } + } + + @Override + public MapCodec codec() { + return EXAMPLE_BIOME_MODIFIER.value(); + } +} + +// In some registration class +private static final DeferredRegister> BIOME_MODIFIERS = + DeferredRegister.create(NeoForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS, MOD_ID); + +public static final Holder> EXAMPLE_BIOME_MODIFIER = + BIOME_MODIFIERS.register("example_biome_modifier", () -> RecordCodecBuilder.mapCodec(instance -> + instance.group( + Biome.LIST_CODEC.fieldOf("biomes").forGetter(ExampleBiomeModifier::biomes), + Codec.INT.fieldOf("value").forGetter(ExampleBiomeModifier::value) + ).apply(instance, ExampleBiomeModifier::new) + )); +``` + + +## Datagenning Biome Modifiers + +A `BiomeModifier` JSON can be created through [data generation][datagen] by passing a `RegistrySetBuilder` to `DatapackBuiltinEntriesProvider`. The JSON will be placed at `data//neoforge/biome_modifier/.json`. + +For more information on how `RegistrySetBuilder` and `DatapackBuiltinEntriesProvider` work, please see the article on [Data Generation for Datapack Registries][datapackdatagen]. + +```java +// Define the ResourceKey for our BiomeModifier. +public static final ResourceKey EXAMPLE_MODIFIER = ResourceKey.create( + NeoForgeRegistries.Keys.BIOME_MODIFIERS, // The registry this key is for + ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_modifier") // The registry name +); + +// BUILDER is a RegistrySetBuilder passed to DatapackBuiltinEntriesProvider +// in a listener for GatherDataEvent. +BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { + // Lookup any necessary registries. + // Static registries only need to be looked up if you need to grab the tag data. + HolderGetter biomes = bootstrap.lookup(Registries.BIOME); + + // Register the biome modifiers. + bootstrap.register(EXAMPLE_MODIFIER, + new ExampleBiomeModifier( + biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), + 20 + ) + ); +}); +``` + +This will then result in the following JSON being created: + +```json5 +// In data/examplemod/neoforge/biome_modifier/example_modifier.json +{ + // The registy key of the MapCodec for the modifier + "type": "examplemod:example_biome_modifier", + // All additional settings are applied to the root object + "biomes": "#c:is_overworld", + "value": 20 +} +``` + +[datareg]: ../concepts/registries.md#datapack-registries +[staticreg]: ../concepts/registries.md#methods-for-registering +[datapacks]: ../resources/index.md#data +[datagen]: ../resources/index.md#data-generation +[datapackdatagen]: ../concepts/registries#data-generation-for-datapack-registries \ No newline at end of file From 56cb721a973f993d3df95cdcc645fc30bc61c030 Mon Sep 17 00:00:00 2001 From: TelepathicGrunt <40846040+TelepathicGrunt@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:21:54 -0400 Subject: [PATCH 9/9] Fix anchors in Biome Modifier docs (#167) --- docs/worldgen/biomemodifier.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/worldgen/biomemodifier.md b/docs/worldgen/biomemodifier.md index f4ad33cb..ba910222 100644 --- a/docs/worldgen/biomemodifier.md +++ b/docs/worldgen/biomemodifier.md @@ -8,27 +8,27 @@ Biome Modifiers are a data-driven system that allows for changing many aspects o ### Recommended Section To Read: - Players or pack developers: - - [Applying Biome Modifiers](#Applying-Biome-Modifiers) - - [Built-in Neoforge Biome Modifiers](#Built-in-Biome-Modifiers) + - [Applying Biome Modifiers](#applying-biome-modifiers) + - [Built-in Neoforge Biome Modifiers](#built-in-biome-modifiers) - Modders doing simple additions or removal biome modifications: - - [Applying Biome Modifiers](#Applying-Biome-Modifiers) - - [Built-in Neoforge Biome Modifiers](#Built-in-Biome-Modifiers) - - [Datagenning Biome Modifiers](#Datagenning-Biome-Modifiers) + - [Applying Biome Modifiers](#applying-biome-modifiers) + - [Built-in Neoforge Biome Modifiers](#built-in-biome-modifiers) + - [Datagenning Biome Modifiers](#datagenning-biome-modifiers) - Modders who want to do custom or complex biome modifications: - - [Applying Biome Modifiers](#Applying-Biome-Modifiers) - - [Creating Custom Biome Modifiers](#Creating-Custom-Biome-Modifiers) - - [Datagenning Biome Modifiers](#Datagenning-Biome-Modifiers) + - [Applying Biome Modifiers](#applying-biome-modifiers) + - [Creating Custom Biome Modifiers](#creating-custom-biome-modifiers) + - [Datagenning Biome Modifiers](#datagenning-biome-modifiers) ## Applying Biome Modifiers To have NeoForge load a biome modifier JSON file into the game, the file will need to be under `data//neoforge/biome_modifier/.json` folder in the mod's resources, or in a [Datapack][datapacks]. Then, once NeoForge loads the biome modifier, it will read its instructions and apply the described modifications to all target biomes when the world is loaded up. Pre-existing biome modifiers from mods can be overridden by datapacks having a new JSON file at the exact same location and name. -The JSON file can be created by hand following the examples in the '[Built-in NeoForge Biome Modifiers](#Built-in-Biome-Modifiers)' section or be datagenned as shown in the '[Datagenning Biome Modifiers](#Datagenning-Biome-Modifiers)' section. +The JSON file can be created by hand following the examples in the '[Built-in NeoForge Biome Modifiers](#built-in-biome-modifiers)' section or be datagenned as shown in the '[Datagenning Biome Modifiers](#datagenning-biome-modifiers)' section. ## Built-in Biome Modifiers