-
-
Notifications
You must be signed in to change notification settings - Fork 66
Add a guide on the upcoming data maps #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
baa8bba
Guide on data maps
Matyrobbrt 95d24fb
Update index.md
Matyrobbrt 5eff852
Update index.md
Matyrobbrt c005ede
Update index.md
Matyrobbrt 69c7b16
Update index.md
Matyrobbrt d1c0e39
Update index.md
Matyrobbrt b372542
Update structure.md
Matyrobbrt 455ff66
Some review comments
Matyrobbrt 1e015ac
Update docusaurus.config.js
Matyrobbrt 1e52938
Update neo_maps.md
Matyrobbrt f22ce03
Stuff
Matyrobbrt 6c040b9
Update docs/datamaps/index.md
Matyrobbrt b40d8f8
Update docs/datamaps/index.md
Matyrobbrt 7674799
Update docs/datamaps/index.md
Matyrobbrt ab01b23
Update docs/datamaps/neo_maps.md
Matyrobbrt 7657202
Update index.md
Matyrobbrt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"label": "Data Maps" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# 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 entry->boolean maps, while data maps are more flexible entry->object maps. | ||
|
||
A data map can be attached to both static, built-in, registries and dynamic data-driven datapack registries. | ||
|
||
## Registration | ||
A data map type should be statically created and then registered to the `RegisterDataMapTypesEvent` (which is fired on the mod event bus). The `DataMapType` can be created using a `DataMapType$Builder`, through `DataMapType#builder`. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
A simple `DataMapType` has two generic arguments: `T` (the values that are being attached) and `R` (the type of the registry the data map is for). A data map of `SomeObject`s that are attached to `Item`s can, as such, be represented as `DataMapType<SomeObject, Item>`. | ||
|
||
Data maps are serialized and deserialized using [Codecs](../datastorage/codecs.md). | ||
|
||
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<DropHealing> 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 should be an *immutable* object, as otherwise weird behaviour can be caused if the object is attached to all values within a tag (since no copy is created). | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
::: | ||
|
||
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<DropHealing, Item> 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<T> networkCodec, boolean mandatory)`. | ||
The values of the data map will then be synced using the `networkCodec`. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 is, on the other side, optional, so it will not prevent any clients from joining. | ||
ChampionAsh5357 marked this conversation as resolved.
Show resolved
Hide resolved
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## JSON Structure and location | ||
Data maps are loaded from a JSON file located at `:mapNamespace/data_maps/:registryNamespace/:registryPath/:mapPath.json`. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
For more information, please [check out the dedicated page](./structure.md). | ||
|
||
## 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. `Named` 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). | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
::: | ||
|
||
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<Item> 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 added 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). | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`AdvancedDataMapType` have one more generic besides `T` and `R`: `VR extends DataMapValueRemover<T, R>`. This additional generic allows you to datagen remove objects with increased type safety, but it can otherwise be ignored and treated as wilcard (`?`) for most use cases. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### 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. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### 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<TagKey<R>, ResourceKey<R>>` 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. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Generally, mergers should simply merge the values, and should not perform "hard" overwrites unless necessary (i.e. if merging isn't possible). A value can be overwritten by specifying the object-level `replace` field, which will bypass the merger. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
We provide some default mergers for merging lists, sets and maps in `DataMapValueMerger`. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The default merger (`DataMapValueMerger#defaultMerger`) has the typical first come last serverd priority-based overwriting behaviour that you'd expect from normal data packs, where the newest value always wins. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### 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 removers that will then be given the value currently attached and its source, and is expected to create a new object that will have the properties requested by the `remove` object removed. Alternatively, an empty `Optional` will lead to the value being completely removed. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
An example of a remover that will remove a value with a specific key from a `Map`-based data map: | ||
ChampionAsh5357 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
```java | ||
public record MapRemover(String key) implements DataMapValueRemover<Map<String, String>, Item> { | ||
public static final Codec<MapRemover> CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key); | ||
|
||
@Override | ||
public Optional<Map<String, String>> remove(Map<String, String> value, Registry<Item> registry, Either<TagKey<Item>, ResourceKey<Item>> source, Item object) { | ||
final Map<String, String> 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: | ||
```js | ||
{ | ||
"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: | ||
```js | ||
{ | ||
"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 through `DataMapProvider`. | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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<HolderLookup.Provider> 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); | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Built-in Data Maps | ||
NeoForge provides a few data maps that mostly replace hardcoded in-code vanilla maps. | ||
These dats maps can be found in `NeoForgeDataMaps`. | ||
|
||
## `neoforge:compostables` | ||
NeoForge provides a data map that allows configuring composter values, as a replacement for `ComposterBlock#COMPOSTABLES` (which is now ignored). | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This data map is located at `neoforged/data_maps/item/compostables.json` and its objects have the following structure: | ||
```js | ||
{ | ||
// A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter | ||
"chance": 1, | ||
|
||
// A 1 to 7 integer that indicates how many levels should be added to the composter when the item is successfully composted. Defaults to 1 level. | ||
"amount": 1 // int | ||
} | ||
``` | ||
|
||
Example: | ||
```js | ||
{ | ||
"values": { | ||
// Give acacia logs a 50% chance that they will fill a composter with 2 levels | ||
"minecraft:acacia_log": { | ||
"chance": 0.5, | ||
"amount": 2 | ||
} | ||
} | ||
} | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# 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](./index.md#registration). | ||
|
||
## Location | ||
Data maps are loaded from a JSON file located at `:mapNamespace/data_maps/:registryNamespace/:registryPath/:mapPath.json`, where: | ||
Matyrobbrt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- `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. | ||
|
||
## 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. | ||
|
||
```js | ||
{ | ||
"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](./index.md#advanced-data-maps). 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: | ||
```js | ||
{ | ||
"values": { | ||
// Overwrite the value of the carrot item | ||
"minecraft:carrot": { | ||
"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: | ||
```js | ||
{ | ||
// 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: | ||
```js | ||
{ | ||
"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](./index.md#advanced-data-maps) 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: | ||
```js | ||
{ | ||
"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" | ||
} | ||
} | ||
``` | ||
::: |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.