diff --git a/docs/datamaps/_category_.json b/docs/datamaps/_category_.json deleted file mode 100644 index 43a13824e..000000000 --- a/docs/datamaps/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Data Maps" -} \ No newline at end of file diff --git a/docs/datamaps/index.md b/docs/datamaps/index.md deleted file mode 100644 index 74cb4a4d3..000000000 --- a/docs/datamaps/index.md +++ /dev/null @@ -1,209 +0,0 @@ -# Data Maps - -A registry data map contains data-driven, reloadable objects that can be attached to a registry object. This system allows more easily data-driving game behaviour, as they provide functionality such as syncing or conflict resolution, leading to a better and more configurable user experience. - -You can think of tags as registry object ➜ boolean maps, while data maps are more flexible registry object ➜ object maps. - -A data map can be attached to both static, built-in, registries and dynamic data-driven datapack registries. - -Data maps support reloading through the use of the `/reload` command or any other means that reload server resources. - -## Registration - -A data map type should be statically created and then registered to the `RegisterDataMapTypesEvent` (which is fired on the [mod event bus][events]). The `DataMapType` can be created using a DataMapType.Builder, through `DataMapType#builder`. - -The builder provides a `synced` method which can be used to mark a data map as synced and have it sent to clients. - -A simple `DataMapType` has two generic arguments: `R` (the type of the registry the data map is for) and `T` (the values that are being attached). A data map of `SomeObject`s that are attached to `Item`s can, as such, be represented as `DataMapType`. - -Data maps are serialized and deserialized using [Codecs][codecs]. - -Let's take the following record representing the data map value as an example: - -```java -public record DropHealing( - float amount, float chance -) { - public static final Codec CODEC = RecordCodecBuilder.create(in -> in.group( - Codec.FLOAT.fieldOf("amount").forGetter(DropHealing::amount), - Codec.floatRange(0, 1).fieldOf("chance").forGetter(DropHealing::chance) - ).apply(in, DropHealing::new)); -} -``` - -:::warning -The value (`T`) should be an *immutable* object, as otherwise weird behaviour can be caused if the object is attached to all entries within a tag (since no copy is created). -::: - -For the purposes of this example, we will use this data map to heal players when they drop an item. The `DataMapType` can be created as such: - -```java -public static final DataMapType DROP_HEALING = DataMapType.builder( - new ResourceLocation("mymod:drop_healing"), Registries.ITEM, DropHealing.CODEC -).build(); -``` - -and then registered to the `RegisterDataMapTypesEvent` using `RegisterDataMapTypesEvent#register`. - -## Syncing - -A synced data map will have its values synced to clients. A data map can be marked as synced using `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)`. The values of the data map will then be synced using the `networkCodec`. If the `mandatory` flag is set to `true`, clients that do not support the data map (including Vanilla clients) will not be able to connect to the server, nor vice-versa. A non-mandatory data map on the other hand is optional, so it will not prevent any clients from joining. - -:::tip -A separate network codec allows for packet sizes to be smaller, as you can choose what data to send, and in what format. Otherwise the default codec can be used. -::: - -## JSON Structure and location - -Data maps are loaded from a JSON file located at `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json`, where: -- `mapNamespace` is the namespace of the ID of the data map -- `mapPath` is the path of the ID of the data map -- `registryNamespace` is the namespace of the ID of the registry; if the namespace is `minecraft`, this value will be omitted -- `registryPath` is the path of the ID of the registry - -For more information, please [check out the dedicated page][structure]. - -## Usage - -As data maps can be used on any registry, they can be queried through `Holder`s, and not through the actual registry objects. You can query a data map value using `Holder#getData(DataMapType)`. If that object doesn't have a value attached, the method will return `null`. - -:::note -Only reference holders will return a value in that method. `Direct` holders will **not**. Generally, you will only encounter reference holders (which are returned by methods such as `Registry#wrapAsHolder`, `Registry#getHolder` or the different `builtInRegistryHolder` methods). -::: - -To continue the example above, we can implement our intended behaviour as follows: - -```java -public static void onItemDrop(final ItemTossEvent event) { - final ItemStack stack = event.getEntity().getItem(); - // ItemStack has a getItemHolder method that will return a Holder which points to the item the stack is of - //highlight-next-line - final DropHealing value = stack.getItemHolder().getData(DROP_HEALING); - // Since getData returns null if the item will not have a drop healing value attached, we guard against it being null - if (value != null) { - // And here we simply use the values - if (event.getPlayer().level().getRandom().nextFloat() > value.chance()) { - event.getPlayer().heal(value.amount()); - } - } -} -``` - -## Advanced data maps - -Advanced data maps are data maps which have additional functionality. Namely, the ability of merging values and selectively removing them, through a remover. Implementing some form of merging and removers is highly recommended for data maps whose values are collection-likes (like `Map`s or `List`s). - -`AdvancedDataMapType` have one more generic besides `T` and `R`: `VR extends DataMapValueRemover`. This additional generic allows you to datagen remove objects with increased type safety. - -### Creation - -You create an `AdvancedDataMapType` using `AdvancedDataMapType#builder`. Unlike the normal builder, the builder returned by that method will have two more methods (`merger` and `remover`), and it will return an `AdvancedDataMapType`. Registration methods remain the same. - -### Mergers - -An advanced data map can provide a `DataMapValueMerger` through `AdvancedDataMapType#merger`. This merger will be used to handle conflicts between data packs that attempt to attach a value to the same object. The merger will be given the two conflicting values, and their sources (as an `Either, ResourceKey>` since values can be attached to all entries within a tag, not just individual entries), and is expected to return the value that will actually be attached. Generally, mergers should simply merge the values, and should not perform "hard" overwrites unless necessary (i.e. if merging isn't possible). If a pack wants to bypass the merger, it can do so by specifying the object-level `replace` field. - -Let's imagine a scenario where we have a data map that attaches integers to items: - -```java -public class IntMerger implements DataMapValueMerger { - @Override - public Integer merge(Registry registry, Either, ResourceKey> first, Integer firstValue, Either, ResourceKey> second, Integer secondValue) { - //highlight-next-line - return firstValue + secondValue; - } -} -``` - -The above merger will merge the values if two datapacks attach to the same object. So if the first pack attaches the value `12` to `minecraft:carrot`, and the second pack attaches the value `15` to `minecraft:carrot`, the final value will be `27`. However, if the second pack specifies the object-level `replace` field, the final value will be `15` as the merger won't be invoked. - -NeoForge provides some default mergers for merging lists, sets and maps in `DataMapValueMerger`. - -The default merger (`DataMapValueMerger#defaultMerger`) has the typical behaviour you'd expect from normal data packs, where the newest value (which comes from the highest datapack) overwrites the previous value. - -### Removers - -An advanced data map can provide a `DataMapValueRemover` through `AdvancedDataMapType#remover`. The remover will allow selective removals of data map values, effectively decomposition. While by default a datapack can only remove the whole object attached to a registry entry, with a remover it can remove just speciffic values from the attached object (i.e. just the entry with a given key in the case of a map, or the entry with a specific property in the case of a list). - -The codec that is passed to the builder will decode remover instances. These removers will then be given the value currently attached and its source, and are expected to create a new object to replace the old value. Alternatively, an empty `Optional` will lead to the value being completely removed. - -An example of a remover that will remove a value with a specific key from a `Map`-based data map: - -```java -public record MapRemover(String key) implements DataMapValueRemover> { - public static final Codec CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key); - - @Override - public Optional> remove(Map value, Registry registry, Either, ResourceKey> source, Item object) { - final Map newMap = new HashMap<>(value); - newMap.remove(key); - return Optional.of(newMap); - } -} -``` - -With the remover above in mind, we're attaching maps of string to string to items. Take the following data map JSON file: - -```json5 -{ - "values": { - //highlight-start - "minecraft:carrot": { - "somekey1": "value1", - "somekey2": "value2" - } - //highlight-end - } -} -``` - -That file will attach the map `[somekey1=value1, somekey2=value2]` to the `minecraft:carrot` item. Now, another datapack can come on top of it and remove just the value with the `somekey1` key, as such: - -```json5 -{ - "remove": { - // As the remover is decoded as a string, we can use a string as the value here. If it were decoded as an object, we would have needed to use an object. - //highlight-next-line - "minecraft:carrot": "somekey1" - } -} -``` - -After the second datapack is read and applied, the new value attached to the `minecraft:carrot` item will be `[somekey2=value2]`. - -## Datagen - -Data maps can be [generated][datagen] through `DataMapProvider`. You should extend that class, and then override the `generate` method to create your entries, similar to tag generation. - -Considering the drop healing example from the start, we could generate some values as follows: - -```java -public class DropHealingGen extends DataMapProvider { - - public DropHealingGen(PackOutput packOutput, CompletableFuture lookupProvider) { - super(packOutput, lookupProvider); - } - - @Override - protected void gather() { - // In the examples below, we do not need to forcibly replace any value as that's the default behaviour since a merger isn't provided, so the third parameter can be false. - - // If you were to provide a merger for your data map, then the third parameter will cause the old value to be overwritten if set to true, without invoking the merger - builder(DROP_HEALING) - // Always give entities that drop any item in the minecraft:fox_food tag 12 hearts - .add(ItemTags.FOX_FOOD, new DropHealing(12, 1f), false) - // Have a 10% chance of healing entities that drop an acacia boat by one point - .add(Items.ACACIA_BOAT.builtInRegistryHolder(), new DropHealing(1, 0.1f), false); - } -} -``` - -:::tip -There are `add` overloads that accept raw `ResourceLocation`s if you want to attach values to objects added by optional dependencies. In that case you should also provide [a loading condition][conditional] through the var-args parameter to avoid crashes. -::: - -[events]: ../concepts/events.md -[codecs]: ../datastorage/codecs.md -[structure]: ./structure.md -[datagen]: ../resources/index.md#data-generation -[conditional]: ../resources/server/conditional.md diff --git a/docs/datamaps/neo_maps.md b/docs/datamaps/neo_maps.md deleted file mode 100644 index 8b6dc1a6f..000000000 --- a/docs/datamaps/neo_maps.md +++ /dev/null @@ -1,155 +0,0 @@ -# Built-in Data Maps - -NeoForge provides a few data maps that mostly replace hardcoded in-code vanilla maps. These data maps can be found in `NeoForgeDataMaps`, and are always *optional* to ensure compatibility with vanilla clients. - -## `neoforge:compostables` - -NeoForge provides a data map that allows configuring composter values, as a replacement for `ComposterBlock#COMPOSTABLES` (which is now ignored). This data map is located at `neoforge/data_maps/item/compostables.json` and its objects have the following structure: - -```json5 -{ - // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter - "chance": 1 -} -``` - -Example: - -```json5 -{ - "values": { - // Give acacia logs a 50% chance that they will fill a composter - "minecraft:acacia_log": { - "chance": 0.5 - } - } -} -``` - -## `neoforge:furnace_fuels` - -NeoForge provides a data map that allows configuring item burn times. This data map is located at `neoforge/data_maps/item/furnace_fuels.json` and its objects have the following structure: - -```json5 -{ - // A positive integer representing the item's burn time, in ticks - "burn_time": 1000 -} -``` - -Example: - -```json5 -{ - "values": { - // Give anvils a 2 seconds burn time - "minecraft:anvil": { - "burn_time": 40 - } - } -} -``` - -:::note -Other in-code methods like `IItemExtension#getBurnTime` will take priority over the data map, so it is recommended that you use the data map for simple, static burn times even in your mod so that users can configure them. -::: - -:::warning -Vanilla adds a burn time to logs and planks in the `minecraft:logs` and `minecraft:planks` tag. However, those tags also contain Nether wood, so a removal for elements in `#minecraft:non_flammable_wood` is added. However, the removal does not affect any values added by other packs or mods, so if you want to change the values for the wood tags you will need to add a removal for the non flammable tag yourself. -::: - -## `neoforge:monster_room_mobs` - -NeoForge provides a data map that allows configuring the mobs which may appear in the mob spawner in a monster room, as a replacement for `MonsterRoomFeature#MOBS` (which is now ignored). This data map is located at `neoforge/data_maps/entity_type/monster_room_mobs.json` and its objects have the following structure: - -```json5 -{ - // The weight of this mob, relative to other mobs in the datamap - "weight": 100 -} -``` - -Example: - -```json5 -{ - "values": { - // Make squids appear in monster room spawners with a weight of 100 - "minecraft:squid": { - "weight": 100 - } - } -} -``` - -## `neoforge:parrot_imitations` - -NeoForge provides a data map that 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: - -```json5 -{ - // The ID of the sound that parrots will produce when imitating the mob - "sound": "minecraft:entity.parrot.imitate.creeper" -} -``` - -Example: - -```json5 -{ - "values": { - // Make parrots produce the ambient cave sound when imitating allays - "minecraft:allay": { - "sound": "minecraft:ambient.cave" - } - } -} -``` - -## `neoforge:raid_hero_gifts` - -NeoForge provides a data map that allows configuring the gift that a villager with a certain `VillagerProfession` may gift you if you stop the raid, as a replacement for `GiveGiftToHero#GIFTS` (which is now ignored). This data map is located at `neoforge/data_maps/villager_profession/raid_hero_gifts.json` and its objects have the following structure: - -```json5 -{ - // The ID of the loot table that a villager profession will hand out after a raid - "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" -} -``` - -Example: - -```json5 -{ - "values": { - "minecraft:armorer": { - // Make armorers give the raid hero the armorer gift loot table - "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" - }, - } -} -``` - -## `neoforge:vibration_frequencies` - -NeoForge provides a data map that allows configuring the shulker vibration frequencies emitted by game events, as a replacement for `VibrationSystem#VIBRATION_FREQUENCY_FOR_EVENT` (which is now ignored). This data map is located at `neoforge/data_maps/game_event/vibration_frequencies.json` and its objects have the following structure: - -```json5 -{ - // An integer between 1 and 15 (inclusive) that indicates the vibration frequency of the event - "frequency": 2 -} -``` - -Example: - -```json5 -{ - "values": { - // Make the splash in water game event vibrate on the second frequency - "minecraft:splash": { - "frequency": 2 - } - } -} -``` diff --git a/docs/datamaps/structure.md b/docs/datamaps/structure.md deleted file mode 100644 index c0c530ba5..000000000 --- a/docs/datamaps/structure.md +++ /dev/null @@ -1,118 +0,0 @@ -# JSON Structure - -For the purposes of this page, we will use a data map which is an object with two float keys: `amount` and `chance` as an example. The codec for that object can be found [here][datamapregistration]. - -## Location - -Data maps are loaded from a JSON file located at `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json`, where: - -- `mapNamespace` is the namespace of the ID of the data map -- `mapPath` is the path of the ID of the data map -- `registryNamespace` is the namespace of the ID of the registry -- `registryPath` is the path of the ID of the registry - -:::note -The registry namespace is ommited if it is `minecraft`. -::: - -Examples: - -- For a data map named `mymod:drop_healing` for the `minecraft:item` registry (as in the example), the path will be `mymod/data_maps/item/drop_healing.json`. -- For a data map named `somemod:somemap` for the `minecraft:block` registry, the path will be `somemod/data_maps/block/somemap.json`. -- For a data map named `example:stuff` for the `somemod:custom` registry, the path will be `example/data_maps/somemod/custom/stuff.json`. - -## Global `replace` field - -The JSON file has an optional, global `replace` field, which is similar to tags, and when `true` will remove all previously attached values of that data map. This is useful for datapacks that want to completely change the entire data map. - -## Loading conditions - -Data map files support [loading conditions][conditional] both at root-level and at entry-level through a `neoforge:conditions` array. - -## Adding values - -Values can be attached to objects using the `values` map. Each key will represent either the ID of an individual registry entry to attach the value to, or a tag key, preceeded by `#`. If it is a tag, the same value will be attached to all entries in that tag. The key will be the object to attach. - -```json5 -{ - "values": { - // Attach a value to the carrot item - "minecraft:carrot": { - "amount": 12, - "chance": 1 - }, - // Attach a value to all items in the logs tag - "#minecraft:logs": { - "amount": 1, - "chance": 0.1 - } - } -} -``` - -:::info -The above structure will invoke mergers in the case of [advanced data maps][advanceddatamaps]. If you do not want to invoke the merger for a specific object, then you will have to use a structure similar to this one: - -```json5 -{ - "values": { - // Overwrite the value of the carrot item - "minecraft:carrot": { - // highlight-next-line - "replace": true, - // The new value will be under a value sub-object - "value": { - "amount": 12, - "chance": 1 - } - } - } -} -``` -::: - -## Removing values - -A JSON file can also remove values previously attached to objects, through the use of the `remove` array: - -```json5 -{ - // Remove the value attached to apples and potatoes - "remove": ["minecraft:apple", "minecraft:potato"] -} -``` - -The array contains a list of registry entry IDs or tags to remove the value from. - -:::warning -Removals happen after the values in the current JSON file have been attached, so you can use the removal feature to remove a value attached to an object through a tag: - -```json5 -{ - "values": { - "#minecraft:logs": 12 - }, - // Remove the value from the acacia log, so that all logs but acacia have the value 12 attached to them - "remove": ["minecraft:acacia_log"] -} -``` -::: - -:::info -In the case of [advanced data maps][advanceddatamaps] that provide a custom remover, the arguments of the remover can be provided by transforming the `remove` array into a map. -Let's assume that the remover object is serialized as a string and removes the value with a given key for a `Map`-based data map: - -```json5 -{ - "remove": { - // The remover will be deserialized from the value (`somekey1` in this case) - // and applied to the value attached to the carrot item - "minecraft:carrot": "somekey1" - } -} -``` -::: - -[datamapregistration]: ./index.md#registration -[conditional]: ../resources/server/conditional.md -[advanceddatamaps]: ./index.md#advanced-data-maps diff --git a/docs/resources/index.md b/docs/resources/index.md index d3b220a97..1bff3ec63 100644 --- a/docs/resources/index.md +++ b/docs/resources/index.md @@ -32,16 +32,16 @@ There is currently no built-in way to apply a set of custom data packs to every Data packs may contain folders with files affecting the following things: -| Folder name | Contents | -|-----------------------------------------------------------------------|------------------------------| -| `advancements` | [Advancements][advancements] | -| `damage_type` | Damage types | -| `loot_tables` | [Loot tables][loottables] | -| `recipes` | [Recipes][recipes] | -| `structures` | Structures | -| `tags` | [Tags][tags] | -| `dimension`, `dimension_type`, `worldgen`, `neoforge/biome_modifiers` | Worldgen files | -| `neoforge/global_loot_modifiers` | [Global loot modifiers][glm] | +| Folder name | Contents | +|-------------------------------------------------------------------------------------|------------------------------| +| `advancements` | [Advancements][advancements] | +| `damage_type` | Damage types | +| `loot_tables` | [Loot tables][loottables] | +| `recipes` | [Recipes][recipes] | +| `tags` | [Tags][tags] | +| `neoforge/data_maps` | [Data maps][datamap] | +| `neoforge/global_loot_modifiers` | [Global loot modifiers][glm] | +| `dimension`, `dimension_type`, `structures`, `worldgen`, `neoforge/biome_modifiers` | Worldgen files | Additionally, they may also contain subfolders for some systems that integrate with commands. These systems are rarely used in conjunction with mods, but worth mentioning regardless: @@ -75,14 +75,14 @@ All data providers extend the `DataProvider` interface and usually require one m | [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | Sound definitions | Client | | | `SpriteSourceProvider` | `gather()` | Sprite sources / atlases | Client | | | [`AdvancementProvider`][advancementprovider] | `generate()` | Advancements | Server | Make sure to use the NeoForge variant, not the Minecraft one. | -| [`LootTableProvider`][loottableprovider] | `generate()` | Loot tables | Server | Requires extra methods and classes to work properly, see linked article for details. | +| [`LootTableProvider`][loottableprovider] | `generate()` | Loot tables | Server | Requires extra methods and classes to work properly, see linked article for details. | | [`RecipeProvider`][recipeprovider] | `buildRecipes(RecipeOutput)` | Recipes | Server | | | [Various subclasses of `TagsProvider`][tagsprovider] | `addTags(HolderLookup.Provider)` | Tags | Server | Several specialized subclasses exist, for example `BlockTagsProvider`. If the one you need doesn't exist, extend `TagsProvider` (or `IntrinsicHolderTagsProvider` if applicable) with your tag type as the generic parameter. | -| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | Datapack builtin entries, e.g. worldgen | Server | See linked article for details. | | [`DataMapProvider`][datamapprovider] | `gather()` | Data map entries | Server | | | [`GlobalLootModifierProvider`][glmprovider] | `start()` | Global loot modifiers | Server | | -| `PackMetadataGenerator` | N/A | `pack.mcmeta` | Both | Metadata sections are added by constructor chaining via `#add` | -| `JsonCodecProvider` (abstract class) | `gather()` | Objects with a codec | Both | This can be extended for use with any object that has a codec to encode data to. | +| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | Datapack builtin entries, e.g. worldgen | Server | See linked article for details. | +| `PackMetadataGenerator` | N/A | `pack.mcmeta` | Both | Metadata sections are added by constructor chaining via `#add` | +| `JsonCodecProvider` (abstract class) | `gather()` | Objects with a codec | Both | This can be extended for use with any object that has a codec to encode data to. | All of these providers follow the same pattern. First, you create a subclass and add your own resources to be generated. Then, you add the provider to the event in an [event handler][eventhandler]. An example using a `RecipeProvider`: @@ -181,8 +181,8 @@ runs { [blockstateprovider]: client/models/datagen.md#block-model-datagen [bsfile]: client/models/index.md#blockstate-files [chattype]: https://minecraft.wiki/w/Chat_type -[datamap]: ../datamaps/index.md -[datamapprovider]: ../datamaps/index.md#datagen +[datamap]: server/datamaps/index.md +[datamapprovider]: server/datamaps/index.md#data-generation [datapackcmd]: https://minecraft.wiki/w/Commands/datapack [datapackprovider]: ../concepts/registries.md#data-generation-for-datapack-registries [event]: ../concepts/events.md diff --git a/docs/resources/server/datamaps/builtin.md b/docs/resources/server/datamaps/builtin.md new file mode 100644 index 000000000..523452923 --- /dev/null +++ b/docs/resources/server/datamaps/builtin.md @@ -0,0 +1,153 @@ +# Built-In Data Maps + +NeoForge provides various built-in [data maps][datamap] for common use cases, replacing hardcoded vanilla fields. Vanilla values are shipped by data map files in NeoForge, so there is no functional difference to the player. + +## `neoforge:compostables` + +Allows configuring composter values, as a replacement for `ComposterBlock.COMPOSTABLES` (which is now ignored). This data map is located at `neoforge/data_maps/item/compostables.json` and its objects have the following structure: + +```json5 +{ + // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter + "chance": 1 +} +``` + +Example: + +```json5 +{ + "values": { + // Give acacia logs a 50% chance that they will fill a composter + "minecraft:acacia_log": { + "chance": 0.5 + } + } +} +``` + +## `neoforge:furnace_fuels` + +Allows configuring item burn times. Note that other in-code methods like `IItemExtension#getBurnTime` will take priority over the data map, so it is recommended that you use the data map for simple, static burn times so that users can configure them. This data map is located at `neoforge/data_maps/item/furnace_fuels.json` and its objects have the following structure: + +```json5 +{ + // A positive integer representing the item's burn time in ticks + "burn_time": 1000 +} +``` + +Example: + +```json5 +{ + "values": { + // Give anvils a 2 seconds burn time + "minecraft:anvil": { + "burn_time": 40 + } + } +} +``` + +:::warning +Vanilla adds an implicit burn time of 300 ticks (15 seconds) for `#minecraft:logs` and `#minecraft:planks`, and then hardcodes the removal of crimson and warped items from that. This means that if you add another non-flammable wood, you should add a removal for that wood type's items from this map. +::: + +## `neoforge:monster_room_mobs` + +Allows configuring the mobs that may appear in the mob spawner in a monster room, as a replacement for `MonsterRoomFeature#MOBS` (which is now ignored). This data map is located at `neoforge/data_maps/entity_type/monster_room_mobs.json` and its objects have the following structure: + +```json5 +{ + // The weight of this mob, relative to other mobs in the datamap + "weight": 100 +} +``` + +Example: + +```json5 +{ + "values": { + // Make squids appear in monster room spawners with a weight of 100 + "minecraft:squid": { + "weight": 100 + } + } +} +``` + +## `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: + +```json5 +{ + // The ID of the sound that parrots will produce when imitating the mob + "sound": "minecraft:entity.parrot.imitate.creeper" +} +``` + +Example: + +```json5 +{ + "values": { + // Make parrots produce the ambient cave sound when imitating allays + "minecraft:allay": { + "sound": "minecraft:ambient.cave" + } + } +} +``` + +## `neoforge:raid_hero_gifts` + +Allows configuring the gift that a villager with a certain `VillagerProfession` may gift you if you stop the raid, as a replacement for `GiveGiftToHero#GIFTS` (which is now ignored). This data map is located at `neoforge/data_maps/villager_profession/raid_hero_gifts.json` and its objects have the following structure: + +```json5 +{ + // The ID of the loot table that a villager profession will hand out after a raid + "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" +} +``` + +Example: + +```json5 +{ + "values": { + "minecraft:armorer": { + // Make armorers give the raid hero the armorer gift loot table + "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" + }, + } +} +``` + +## `neoforge:vibration_frequencies` + +Allows configuring the sculk vibration frequencies emitted by game events, as a replacement for `VibrationSystem#VIBRATION_FREQUENCY_FOR_EVENT` (which is now ignored). This data map is located at `neoforge/data_maps/game_event/vibration_frequencies.json` and its objects have the following structure: + +```json5 +{ + // An integer between 1 and 15 (inclusive) that indicates the vibration frequency of the event + "frequency": 2 +} +``` + +Example: + +```json5 +{ + "values": { + // Make the splash in water game event vibrate on the second frequency + "minecraft:splash": { + "frequency": 2 + } + } +} +``` + +[datamap]: index.md diff --git a/docs/resources/server/datamaps/index.md b/docs/resources/server/datamaps/index.md new file mode 100644 index 000000000..b0d072033 --- /dev/null +++ b/docs/resources/server/datamaps/index.md @@ -0,0 +1,353 @@ +# Data Maps + +A data map contains data-driven, reloadable objects that can be attached to a registered object. This system allows for more easily data-driving game behaviour, as they provide functionality such as syncing or conflict resolution, leading to a better and more configurable user experience. You can think of [tags] as registry object ➜ boolean maps, while data maps are more flexible registry object ➜ object maps. Similar to [tags], data maps will add to their corresponding data map rather than overwriting. + +A data map can be attached to both static, built-in, registries and dynamic data-driven datapack registries. Data maps support reloading through the use of the `/reload` command or any other means that reload server resources. + +NeoForge provides various [built-in data maps][builtin] for common use cases, replacing hardcoded vanilla fields. More info can be found in the linked article. + +## File Location + +Data maps are loaded from a JSON file located at `/data_maps///.json`, where: + +- `` is the namespace of the ID of the data map, +- `` is the path of the ID of the data map, +- `` is the namespace of the ID of the registry (omitted if it is `minecraft`), and +- `` is the path of the ID of the registry. + +Examples: + +- For a data map named `mymod:drop_healing` for the `minecraft:item` registry (as in the example below), the path will be `mymod/data_maps/item/drop_healing.json`. +- For a data map named `somemod:somemap` for the `minecraft:block` registry, the path will be `somemod/data_maps/block/somemap.json`. +- For a data map named `example:stuff` for the `somemod:custom` registry, the path will be `example/data_maps/somemod/custom/stuff.json`. + +## JSON Structure + +A data map file itself may contain the following fields: + +- `replace`: A boolean that will clear the data map before adding the values of this file. This should never be shipped by mods, and only be used by pack developers that want to overwrite this map for their own purposes. +- `neoforge:conditions`: A list of [loading conditions][conditional]. +- `values`: A map of registry IDs or tag IDs to values that should be added to the data map by your mod. The structure of the values themselves is defined by the data map's codec (see below). +- `remove`: A list of registry IDs or tag IDs to be removed from the data map. + +### Adding Values + +For example, let's assume that we have a data map object with two float keys `amount` and `chance` for the registry `minecraft:item`. A corresponding data map file could look something like this: + +```json5 +{ + "values": { + // Attach a value to the carrot item + "minecraft:carrot": { + "amount": 12, + "chance": 1 + }, + // Attach a value to all items in the logs tag + "#minecraft:logs": { + "amount": 1, + "chance": 0.1 + } + } +} +``` + +Data maps may support [mergers][mergers], which will cause custom merging behavior in the case of a conflict, e.g. if two mods add a data map value for the same item. To avoid the merger from triggering, we can specify the `replace` field on the element level, like so: + +```json5 +{ + "values": { + // Overwrite the value of the carrot item + "minecraft:carrot": { + // highlight-next-line + "replace": true, + // The new value will be under a value sub-object + "value": { + "amount": 12, + "chance": 1 + } + } + } +} +``` + +### Removing Existing Values + +Removing elements can be done by specifying a list of item IDs or tag IDs to remove: + +```json5 +{ + // We do not want the potato to have a value, even if another mod's data map added it + "remove": ["minecraft:potato"] +} +``` + +Removals run after additions, so we can include a tag and then exclude certain elements from it again: + +```json5 +{ + "values": { + "#minecraft:logs": { /* ... */ } + }, + // Exclude crimson stem again + "remove": ["minecraft:crimson_stem"] +} +``` + +Data maps may support custom [removers] with additional arguments. To supply these, the `remove` list can be transformed into a JSON object that contains the to-be-removed elements as map keys and the additional data as the associated value. For example, let's assume that our remover object is serialized to a string, then our remover map could look something like this: + +```json5 +{ + "remove": { + // The remover will be deserialized from the value (`somekey1` in this case) + // and applied to the value attached to the carrot item + "minecraft:carrot": "somekey1" + } +} +``` + +## Custom Data Maps + +To begin, we define the format of our data map entries. **Data map entries must be immutable**, making records ideal for this. Reiterating our example from above with two float values `amount` and `chance`, our data map entries will look something like this: + +```java +public record ExampleData(float amount, float chance) {} +``` + +Like many other things, data maps are serialized and deserialized using [codecs]. This means that we need to provide a codec for our data map entry that we will use in a bit: + +```java +public record ExampleData(float amount, float chance) { + public static final Codec CODEC = RecordCodecBuilder(instance -> instance.group( + Codec.FLOAT.fieldOf("amount").forGetter(ExampleData::amount), + Codec.floatRange(0, 1).fieldOf("chance").forGetter(ExampleData::chance) + ).apply(instance, ExampleData::new)); +} +``` + +Next, we create the data map itself: + +```java +// In this example, we register the data map for the minecraft:item registry, hence we use Item as the generic. +// Adjust the types accordingly if you want to create a data map for a different registry. +public static final DataMapType EXAMPLE_DATA = DataMapType.builder( + // The ID of the data map. Data map files for this data map will be located at + // :examplemod/data_maps/item/example_data.json. + new ResourceLocation("examplemod", "example_data"), + // The registry to register the data map for. + Registries.ITEM, + // The codec of the data map entries. + ExampleData.CODEC +).build(); +``` + +Finally, register the data map during the [`RegisterDataMapTypesEvent`][events] on the [mod event bus][modbus]: + +```java +@SubscribeEvent +private static void registerDataMapTypes(RegisterDataMapTypesEvent event) { + event.register(EXAMPLE_DATA); +} +``` + +### Syncing + +Synced data maps will have their values synced to clients. A data map can be marked as synced by calling `#synced` on the builder, like so: + +```java +public static final DataMapType EXAMPLE_DATA = DataMapType.builder(...) + .synced( + // The codec used for syncing. May be identical to the normal codec, but may also be + // a codec with less fields, omitting parts of the object that are not required on the client. + ExampleData.CODEC, + // Whether the data map is mandatory or not. Marking a data map as mandatory will disconnect clients + // that are missing the data map on their side; this includes vanilla clients. + false + ).build(); +``` + +### Usage + +As data maps can be used on any registry, they must be queried through `Holder`s, not through actual registry objects. Moreover, it will only work for reference holders, not `Direct` holders. However, most places will return a reference holder, for example `Registry#wrapAsHolder`, `Registry#getHolder` or the different `builtInRegistryHolder` methods, so in most situations this shouldn't be a problem. + +You can then query the data map value via `Holder#getData(DataMapType)`. If an object does not have a data map value attached, the method will return `null`. Reusing our `ExampleData` from before, let's use them to heal the player whenever he picks them up: + +```java +@SubscribeEvent +private static void itemPickup(ItemPickupEvent event) { + ItemStack stack = event.getItemStack(); + // Get a Holder via ItemStack#getItemHolder. + Holder holder = stack.getItemHolder(); + // Get the data from the holder. + //highlight-next-line + ExampleData data = holder.getData(EXAMPLE_DATA); + if (data != null) { + // The values are present, so let's do something with them! + Player player = event.getPlayer(); + if (player.getLevel().getRandom().nextFloat() > data.chance()) { + player.heal(data.amount()); + } + } +} +``` + +This process of course also works for all data maps provided by NeoForge. + +## Advanced Data Maps + +Advanced data maps are data maps that use `AdvancedDataMapType` instead of the standard `DataMapType` (of which `AdvancedDataMapType` is a subclass). They have some extra functionality, namely the ability to specify custom mergers and custom removers. Implementing this is highly recommended for data maps whose values are collections or collection-likes, such as `List`s or `Map`s. + +While `DataMapType` has two generics `R` (registry type) and `T` (data map value type), `AdvancedDataMapType` has one more: `VR extends DataMapValueRemover`. This generic allows for datagenning removers with proper type safety. + +`AdvancedDataMapType`s are created using `AdvancedDataMapType#builder()` instead of `DataMapType#builder()`, returning an `AdvancedDataMapType.Builder`. This builder has two extra methods `#remover` and `#merger` for specifying removers and mergers (see below), respectively. All other functionality, including syncing, remains the same. + +### Mergers + +A merger can be used to handle conflicts between multiple data packs that attempt to add a value for the same object. The default merger (`DataMapValueMerger#defaultMerger`) will overwrite existing values (from e.g. data packs with lower priority) with new values, so a custom merger is necessary if this isn't the desired behavior. + +The merger will be given the two conflicting values, as well as the objects the values are being attached to (as an `Either, ResourceKey>`, since values can be attached to all objects in a tag or a single object) and the object's owning registry, and should return the value that should actually be attached. Generally, mergers should simply merge and not perform overwrites if possible (i.e. only if merging the normal way doesn't work). If a data pack wants to bypass the merger, it should specify the `replace` field on the object (see [Adding Values][add]). + +Let's imagine a scenario where we have a data map that adds integers to items. We could then simply resolve conflicts by adding both values, like so: + +```java +public class IntMerger implements DataMapValueMerger { + @Override + public Integer merge(Registry registry, + Either, ResourceKey> first, Integer firstValue, + Either, ResourceKey> second, Integer secondValue) { + return firstValue + secondValue; + } +} +``` + +This way, if one pack specifies the value 12 for `minecraft:carrot` and another pack specifies the value 15 for `minecraft:carrot`, then the final value for `minecraft:carrot` will be 27. If either of these objects specify `"replace": true`, then that object's value will be used. If both specify `"replace": true`, then the higher datapack's value is used. + +Finally, don't forget to actually specify the merger in the builder, like so: + +```java +// We assume AdvancedData contains an integer property of some sort. +AdvancedDataMapType ADVANCED_MAP = AdvancedDataMapType.builder(...) + .merger(new IntMerger()) + .build(); +``` + +:::tip +NeoForge provides default mergers for lists, sets and maps in `DataMapValueMerger`. +::: + +### Removers + +Similar to mergers for more complex data, removers can be used for proper handling of `remove` clauses for an element. The default remover (`DataMapValueRemover.Default.INSTANCE`) will simply remove any and all information related to the specified object, so we want to use a custom remover to remove only parts of the object's data. + +The codec passed to the builder (read on) will be used to decode remover instances. The remover will then be passed the value currently attached to the object and its source, and should return an `Optional` of the value to replace the old value. Alternatively, an empty `Optional` will lead to the value being actually removed. + +Consider the following example of a remover that will remove a value with a specific key from a `Map`-based data map: + +```java +public record MapRemover(String key) implements DataMapValueRemover> { + public static final Codec CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key); + + @Override + public Optional> remove(Map value, Registry registry, Either, ResourceKey> source, Item object) { + final Map newMap = new HashMap<>(value); + newMap.remove(key); + return Optional.of(newMap); + } +} +``` + +With this remover in mind, consider the following data file: + +```json5 +{ + "values": { + "minecraft:carrot": { + "somekey1": "value1", + "somekey2": "value2" + } + } +} +``` + +Now, consider this second data file that is placed at a higher priority than the first one: + +```json5 +{ + "remove": { + // As the remover is decoded as a string, we can use a string as the value here. + // If it were decoded as an object, we would have needed to use an object. + "minecraft:carrot": "somekey1" + } +} +``` + +That way, after both files are applied, the final result will be (an in-memory representation of) this: + +```json5 +{ + "values": { + "minecraft:carrot": { + "somekey1": "value1" + } + } +} +``` + +As with mergers, don't forget to add them to the builder. Note that we simply use the codec here: + +```java +// We assume AdvancedData contains a Map property of some sort. +AdvancedDataMapType ADVANCED_MAP = AdvancedDataMapType.builder(...) + .remover(MapRemover.CODEC) + .build(); +``` + +## Data Generation + +Data maps can be [datagenned][datagen] by extending `DataMapProvider` and overriding `#gather` to create your entries. Reusing the `ExampleData` from before (with float values `amount` and `chance`), our datagen file could look something like this: + +```java +public class MyDataMapProvider extends DataMapProvider { + public MyDataMapProvider(PackOutput packOutput, CompletableFuture lookupProvider) { + super(packOutput, lookupProvider); + } + + @Override + protected void gather() { + // We create a builder for the EXAMPLE_DATA data map and add our entries using #add. + builder(EXAMPLE_DATA) + // We add the value "amount": 10, "chance": 1 for all slabs. The boolean parameter controls + // the "replace" field, which should always be false in a mod. + .add(ItemTags.SLABS, new ExampleData(10, 1), false) + // We add the value "amount": 5, "chance": 0.2 for apples. + .add(Items.APPLE, new ExampleData(5, 0.2f), false); + } +} +``` + +Like all data providers, don't forget to add the provider to the event: + +```java +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + CompletableFuture lookupProvider = event.getLookupProvider(); + + // other providers here + generator.addProvider( + event.includeServer(), + new MyDataMapProvider(output, lookupProvider) + ); +} +``` + +[builtin]: builtin.md +[codecs]: ../../../datastorage/codecs.md +[conditional]: ../conditional.md +[datagen]: ../../index.md#data-generation +[events]: ../../../concepts/events.md +[add]: #adding-values +[mergers]: #mergers +[modbus]: ../../../concepts/events.md#event-buses +[removers]: #removers +[tags]: ../tags.md