Difference between revisions of "Capabilities/Attaching"

From Forge Community Wiki
(Inital Import dokuwiki)
 
(Update to 1.18)
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
As mentioned, attaching capabilities to objects that you have not created can be done using <code>AttachCapabilitiesEvent</code>. The same event is used for all objects that can provide capabilities. <code>AttachCapabilitiesEvent</code> has five valid generic types providing the following events:
 
As mentioned, attaching capabilities to objects that you have not created can be done using <code>AttachCapabilitiesEvent</code>. The same event is used for all objects that can provide capabilities. <code>AttachCapabilitiesEvent</code> has five valid generic types providing the following events:
 
* <code><nowiki>AttachCapabilitiesEvent<Entity></nowiki></code>: Fires only for entities.
 
* <code><nowiki>AttachCapabilitiesEvent<Entity></nowiki></code>: Fires only for entities.
* <code><nowiki>AttachCapabilitiesEvent<TileEntity></nowiki></code>: Fires only for tile entities.
+
* <code><nowiki>AttachCapabilitiesEvent<BlockEntity></nowiki></code>: Fires only for block entities.
 
* <code><nowiki>AttachCapabilitiesEvent<ItemStack></nowiki></code>: Fires only for item stacks.
 
* <code><nowiki>AttachCapabilitiesEvent<ItemStack></nowiki></code>: Fires only for item stacks.
* <code><nowiki>AttachCapabilitiesEvent<World></nowiki></code>: Fires only for worlds.
+
* <code><nowiki>AttachCapabilitiesEvent<Level></nowiki></code>: Fires only for levels.
* <code><nowiki>AttachCapabilitiesEvent<Chunk></nowiki></code>: Fires only for chunks.
+
* <code><nowiki>AttachCapabilitiesEvent<LevelChunk></nowiki></code>: Fires only for level chunks.
  
The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to <code>PlayerEntity</code>, you have to subscribe to the <code><nowiki>AttachCapabilitiesEvent<Entity></nowiki></code>, and then determine that the provided object is an <code>PlayerEntity</code> before attaching the capability.
+
The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to <code>Player</code>, you have to subscribe to the <code><nowiki>AttachCapabilitiesEvent<Entity></nowiki></code>, and then determine that the provided object is an <code>Player</code> before attaching the capability.
  
In all cases, the event has a method <code>addCapability</code>, which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement <code>ICapabilityProvider</code>, if the capability needs to store data persistently it is possible to implement <code><nowiki>ICapabilitySerializable<T extends INBT></nowiki></code> which, on top of returning the capabilities, will allow providing NBT save/load functions.
+
In all cases, the event has a method <code>addCapability</code>, which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement <code>ICapabilityProvider</code>, if the capability needs to store data persistently it is possible to implement <code><nowiki>ICapabilitySerializable<T extends Tag></nowiki></code> which, on top of returning the capabilities, will allow providing NBT save/load functions.
  
 
For information on how to implement <code>ICapabilityProvider</code>, refer to the [[Capabilities|a Capability]] section.
 
For information on how to implement <code>ICapabilityProvider</code>, refer to the [[Capabilities|a Capability]] section.
  
 
== Creating Your Own Capability ==
 
== Creating Your Own Capability ==
In general terms, a capability is declared and registered through a single method call to <code><nowiki>CapabilityManager#INSTANCE#register</nowiki></code>. One possibility is to define a static <code><nowiki>register</nowiki></code> method inside a dedicated class for the capability, but this is not required by the capability system. For the purpose of this documentation we will be describing each part as a separate named class, although anonymous classes are an option.
+
In general terms, a capability is registered through the event <code><nowiki>RegisterCapabilitiesEvent</nowiki></code> on the <code>MOD</code> event bus via the <code>#register</code> method.
<syntaxhighlight lang="java">
 
CapabilityManager.INSTANCE.register(capability_interface_class, storage, default_implementation_factory);
 
</syntaxhighlight>
 
The first parameter to this method, is the type that describes the capability feature. In our example, this will be <code>IExampleCapability.class</code>.
 
  
The second parameter is an instance of a class that implements <code><nowiki>Capability.IStorage<T></nowiki></code>, where T is the same class we specified in the first parameter. This storage class will help manage saving and loading for the default implementation, and it can, optionally, also support other implementations.
 
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
private static class Storage
+
@SubscribeEvent
    implements Capability.IStorage<IExampleCapability> {
+
public void registerCaps(RegisterCapabilitiesEvent event) {
 
+
     event.register(IExampleCapability.class);
  @Override
 
  public INBT writeNBT(Capability<IExampleCapability> capability, IExampleCapability instance, Direction side) {
 
     // return an NBT tag
 
  }
 
 
 
  @Override
 
  public void readNBT(Capability<IExampleCapability> capability, IExampleCapability instance, Direction side, INBT nbt) {
 
    // load from the NBT tag
 
  }
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
The last parameter is a callable factory that will return new instances of the default implementation.
 
<syntaxhighlight lang="java">
 
private static class Factory implements Callable<IExampleCapability> {
 
  
  @Override
+
== Persisting LevelChunk and BlockEntity capabilities ==
  public IExampleCapability call() throws Exception {
+
Unlike <code>Levels</code>, <code>Entities</code> and <code>ItemStacks</code>, <code>LevelChunks</code> and <code>BlockEntities</code> are only written to disk when they have been marked as dirty. A <code>capability</code> implementation with persistent state for a <code>LevelChunk</code> or a <code>BlockEntity</code> should therefore ensure that whenever its state changes, its owner is marked as dirty.
    return new Implementation();
 
  }
 
}
 
</syntaxhighlight>
 
Finally, we will need the default implementation itself, to be able to instantiate it in the factory. Designing this class is up to you, but it should at least provide a basic skeleton that people can use to test the capability, if it’s not a fully usable implementation on itself.
 
 
 
== Persisting Chunk and TileEntity capabilities ==
 
Unlike <code>Worlds</code>, <code>Entities</code> and <code>ItemStacks</code>, <code>Chunks</code> and <code>TileEntities</code> are only written to disk when they have been marked as dirty. A <code>capability</code> implementation with persistent state for a <code>Chunk</code> or a <code>TileEntity</code> should therefore ensure that whenever its state changes, its owner is marked as dirty.
 
  
<code>ItemStackHandler</code>, commonly used for inventories in <code>TileEntities</code>, has an overridable method <code><nowiki>void onContentsChanged(int slot)</nowiki></code> designed to be used to mark the <code>TileEntity</code> as dirty.
+
<code>ItemStackHandler</code>, commonly used for inventories in <code>BlockEntities</code>, has an overridable method <code><nowiki>void onContentsChanged(int slot)</nowiki></code> designed to be used to mark the <code>BlockEntity</code> as dirty.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
public class MyTileEntity extends TileEntity {
+
public class MyBlockEntity extends BlockEntity {
  
 
   private final IItemHandler inventory = new ItemStackHandler(...) {
 
   private final IItemHandler inventory = new ItemStackHandler(...) {
Line 58: Line 33:
 
     protected void onContentsChanged(int slot) {
 
     protected void onContentsChanged(int slot) {
 
       super.onContentsChanged(slot);
 
       super.onContentsChanged(slot);
       markDirty();
+
       setChanged();
 
     }
 
     }
 
   }
 
   }
Line 69: Line 44:
  
 
There are three different situation in which you may want to send synchronization packets, all of them optional:
 
There are three different situation in which you may want to send synchronization packets, all of them optional:
# When the entity spawns in the world, or the block is placed, you may want to share the initialization-assigned values with the clients.
+
# When the entity spawns in the level, or the block is placed, you may want to share the initialization-assigned values with the clients.
 
# When the stored data changes, you may want to notify some or all of the watching clients.
 
# When the stored data changes, you may want to notify some or all of the watching clients.
 
# When a new client starts viewing the entity or block, you may want to notify it of the existing data.
 
# When a new client starts viewing the entity or block, you may want to notify it of the existing data.
Line 78: Line 53:
 
By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process.
 
By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process.
  
This can be done by handling the <code><nowiki>PlayerEvent.Clone</nowiki></code> event, reading the data from the original entity, and assigning it to the new entity. In this event, the <code>wasDead</code> field 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.
+
This can be done by handling the <code><nowiki>PlayerEvent$Clone</nowiki></code> event, reading the data from the original entity, and assigning it to the new entity. In this event, the <code>#isWasDeath</code> 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.
 +
 
 +
 
 +
[[Category:Data Storage]]

Latest revision as of 13:52, 12 January 2022

As mentioned, attaching capabilities to objects that you have not created can be done using AttachCapabilitiesEvent. The same event is used for all objects that can provide capabilities. AttachCapabilitiesEvent has five valid generic types providing the following events:

  • AttachCapabilitiesEvent<Entity>: Fires only for entities.
  • AttachCapabilitiesEvent<BlockEntity>: Fires only for block entities.
  • AttachCapabilitiesEvent<ItemStack>: Fires only for item stacks.
  • AttachCapabilitiesEvent<Level>: Fires only for levels.
  • AttachCapabilitiesEvent<LevelChunk>: Fires only for level chunks.

The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to Player, you have to subscribe to the AttachCapabilitiesEvent<Entity>, and then determine that the provided object is an Player before attaching the capability.

In all cases, the event has a method addCapability, which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement ICapabilityProvider, if the capability needs to store data persistently it is possible to implement ICapabilitySerializable<T extends Tag> which, on top of returning the capabilities, will allow providing NBT save/load functions.

For information on how to implement ICapabilityProvider, refer to the a Capability section.

Creating Your Own Capability

In general terms, a capability is registered through the event RegisterCapabilitiesEvent on the MOD event bus via the #register method.

@SubscribeEvent
public void registerCaps(RegisterCapabilitiesEvent event) {
    event.register(IExampleCapability.class);
}

Persisting LevelChunk and BlockEntity capabilities

Unlike Levels, Entities and ItemStacks, LevelChunks and BlockEntities are only written to disk when they have been marked as dirty. A capability implementation with persistent state for a LevelChunk or a BlockEntity should therefore ensure that whenever its state changes, its owner is marked as dirty.

ItemStackHandler, commonly used for inventories in BlockEntities, has an overridable method void onContentsChanged(int slot) designed to be used to mark the BlockEntity as dirty.

public class MyBlockEntity extends BlockEntity {

  private final IItemHandler inventory = new ItemStackHandler(...) {
    @Override
    protected void onContentsChanged(int slot) {
      super.onContentsChanged(slot);
      setChanged();
    }
  }

  ...
}

Synchronizing Data with Clients

By default, Capability data is not sent to clients. In order to change this, the mods have to manage their own synchronization code using packets.

There are three different situation in which you may want to send synchronization packets, all of them optional:

  1. When the entity spawns in the level, or the block is placed, you may want to share the initialization-assigned values with the clients.
  2. When the stored data changes, you may want to notify some or all of the watching clients.
  3. When a new client starts viewing the entity or block, you may want to notify it of the existing data.

Refer to the Networking page for more information on implementing network packets.

Persisting across Player Deaths

By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process.

This can be done by handling the PlayerEvent$Clone event, 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.