Skip to content

Commit eb25c17

Browse files
authored
Update docs for capability rework (#34)
1 parent e682e9d commit eb25c17

File tree

7 files changed

+372
-117
lines changed

7 files changed

+372
-117
lines changed

docs/concepts/events.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ private void modEventHandler(RegisterEvent event) {
2727
}
2828

2929
// This event is on the forge bus
30-
private static void forgeEventHandler(AttachCapabilitiesEvent<Entity> event) {
30+
private static void forgeEventHandler(ExplosionEvent.Detonate event) {
3131
// ...
3232
}
3333

3434
// In the mod constructor
3535
modEventBus.addListener(this::modEventHandler);
36-
forgeEventBus.addGenericListener(Entity.class, ExampleMod::forgeEventHandler);
36+
forgeEventBus.addListener(ExampleMod::forgeEventHandler);
3737
```
3838

3939
### Instance Annotated Event Handlers

docs/concepts/lifecycle.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ If the game is setup to run [data generators][datagen], then the `GatherDataEven
4747
Common Setup
4848
------------
4949

50-
`FMLCommonSetupEvent` is for actions that are common to both physical client and server, such as registering [capabilities][capabilities].
50+
`FMLCommonSetupEvent` is for actions that are common to both physical client and server.
51+
This event is fired for multiple mods in parallel, so be careful.
52+
You can use `event.enqueueWork(() -> /* do something */)` to run code that is not thread-safe.
5153

5254
Sided Setup
5355
-----------
@@ -70,7 +72,6 @@ There are two other lifecycle events: `FMLConstructModEvent`, fired directly aft
7072
:::
7173

7274
[registering]: ./registries.md#methods-for-registering
73-
[capabilities]: ../datastorage/capabilities.md
7475
[datagen]: ../datagen/index.md
7576
[imc]: ./lifecycle.md#intermodcomms
7677
[sides]: ./sides.md

docs/datastorage/attachments.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Data Attachments
2+
3+
The data attachment system allows mods to attach and store additional data on block entities, chunks, entities, and item stacks.
4+
5+
_To store additional level data, you can use [SavedData](saveddata)._
6+
7+
## Creating an attachment type
8+
9+
To use the system, you need to register an `AttachmentType`.
10+
The attachment type contains the following configuration:
11+
- A default value supplier to create the instance when the data is first accessed. Also used to compare stacks that have the data with stacks that don't have it.
12+
- An optional serializer if the attachment should be persisted.
13+
- (If a serializer was configured) The `copyOnDeath` flag to automatically copy entity data on death (see below).
14+
- (Advanced) (If a serializer was configured) A custom `comparator` to use when checking if the data is the same for two item stacks.
15+
16+
:::tip
17+
If you don't want your attachment to persist, do not provide a serializer.
18+
:::
19+
20+
There are a few ways to provide an attachment serializer: directly implementing `IAttachmentSerializer`, implementing `INBTSerializable` and using the static `AttachmentSerializer.serializable()` method to create the builder, or providing a codec to the builder.
21+
22+
:::warning
23+
Avoid serialization with codecs for item stack attachments, as it is comparatively slow.
24+
:::
25+
26+
In any case, the attachment **must be registered** to the `NeoForgeRegistries.ATTACHMENT_TYPES` registry. Here is an example:
27+
```java
28+
// Create the DeferredRegister for attachment types
29+
private static final DeferredRegister<AttachmentType<?>> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MOD_ID);
30+
31+
// Serialization via INBTSerializable
32+
private static final Supplier<AttachmentType<ItemStackHandler>> HANDLER = ATTACHMENT_TYPES.register(
33+
"handler", () -> AttachmentType.serializable(() -> new ItemStackHandler(1)).build());
34+
// Serialization via codec
35+
private static final Supplier<AttachmentType<Integer>> MANA = ATTACHMENT_TYPES.register(
36+
"mana", () -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build());
37+
// No serialization
38+
private static final Supplier<AttachmentType<SomeCache>> SOME_CACHE = ATTACHMENT_TYPES.register(
39+
"some_cache", () -> AttachmentType.builder(() -> new SomeCache()).build()
40+
);
41+
42+
// In your mod constructor, don't forget to register the DeferredRegister to your mod bus:
43+
ATTACHMENT_TYPES.register(modBus);
44+
```
45+
46+
## Using the attachment type
47+
48+
Once the attachment type is registered, it can be used on any holder object.
49+
Calling `getData` if no data is present will attach a new default instance.
50+
51+
```java
52+
// Get the ItemStackHandler if it already exists, else attach a new one:
53+
ItemStackHandler stackHandler = stack.getData(HANDLER);
54+
// Get the current player mana if it is available, else attach 0:
55+
int playerMana = player.getData(MANA);
56+
// And so on...
57+
```
58+
59+
If attaching a default instance is not desired, a `hasData` check can be added:
60+
```java
61+
// Check if the stack has the HANDLER attachment before doing anything.
62+
if (stack.hasData(HANDLER)) {
63+
ItemStackHandler stackHandler = stack.getData(HANDLER);
64+
// Do something with stack.getData(HANDLER).
65+
}
66+
```
67+
68+
The data can also be updated with `setData`:
69+
```java
70+
// Increment mana by 10.
71+
player.setData(MANA, player.getData(MANA) + 10);
72+
```
73+
74+
:::important
75+
Usually, block entities and chunks need to be marked as dirty when they are modified (with `setChanged` and `setUnsaved(true)`). This is done automatically for calls to `setData`:
76+
```java
77+
chunk.setData(MANA, chunk.getData(MANA) + 10); // will call setUnsaved automatically
78+
```
79+
but if you modify some data that you obtained from `getData` (including a newly created default instance) then you must mark block entities and chunks as dirty explicitly:
80+
```java
81+
var mana = chunk.getData(MUTABLE_MANA);
82+
mana.set(10);
83+
chunk.setUnsaved(true); // must be done manually because we did not use setData
84+
```
85+
:::
86+
87+
## Sharing data with the client
88+
Currently, only serializable item stack attachments are synced between the client and the server.
89+
This is done automatically.
90+
91+
To sync block entity, chunk, or entity attachments to a client, you need to [send a packet to the client][network] yourself.
92+
For chunks, you can use `ChunkWatchEvent.Sent` to know when to send chunk data to a player.
93+
94+
## Copying data on player death
95+
By default, entity data attachments are not copied on player death.
96+
To automatically copy an attachment on player death, set `.copyOnDeath()` in the attachment builder.
97+
98+
More complex handling can be implemented via `PlayerEvent.Clone` by reading the data from the original entity and assigning it to the new entity. In this event, the `#isWasDeath` method can be used to distinguish between respawning after death and returning from the End. This is important because the data will already exist when returning from the End, so care has to be taken to not duplicate values in this case.
99+
100+
For example:
101+
```java
102+
NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> {
103+
if (event.isWasDeath() && event.getOriginal().hasData(MY_DATA)) {
104+
event.getEntity().getData(MY_DATA).fieldToCopy = event.getOriginal().getData(MY_DATA).fieldToCopy;
105+
}
106+
});
107+
```
108+
109+
[network]: ../networking/index.md

0 commit comments

Comments
 (0)