|
|
Line 1: |
Line 1: |
− | '''Capabilities''' are a Forge system that allows cross-mod interactions by allowing capability ''providers'' to
| + | #REDIRECT [[Capabilities]] |
− | dynamically respect contracts and provide specialized behavior without requiring the implementation of many interfaces
| |
− | or hard dependencies on mods.
| |
− | | |
− | == History ==
| |
− | In an ideal world, all that would be needed for a mod to provide the equivalent of a capability would be implementing an
| |
− | interface. This is in fact how cross-mod interaction used to work prior to the introduction of capabilities.
| |
− | | |
− | The real world, though, is often much more complicated: users wanted to be free to combine mods the way they wanted and
| |
− | saw fit, and developers wanted to be able to declare ''soft'' dependencies on other mods, thus reducing the need of
| |
− | having a huge mod pack just for testing.
| |
− | | |
− | The first approach used by Forge was conditional stripping of interfaces and methods, but this proved to be problematic.
| |
− | While the idea works well in theory, in practice the ASM editing of classes relied on complex mechanics and could lead
| |
− | to hard to spot bugs.
| |
− | | |
− | For this reason, the entire system was redesigned and the concept of '''capabilities''' was born.
| |
− | | |
− | == The Concept ==
| |
− | A capability allows any capability provider to conditionally expose a certain ability to do something, e.g. accepting
| |
− | power or handling items. A capability provider, moreover, can decide to expose a capability only on certain sides,
| |
− | allowing for easy interactions with hoppers, cables, etc.
| |
− | | |
− | Capabilities may also be added and removed dynamically both from the "owner" of the capability provider and other mods,
| |
− | allowing even easier cross-mod interaction. For example, a mod that isn't compatible with Forge Energy could be
| |
− | converted into one by dynamically attaching the Forge Energy capability and handling the conversion to a third-party
| |
− | energy system without having to alter the original mod.
| |
− | | |
− | == Terminology ==
| |
− | The high flexibility of the system comes with a cost, though, which is terminology. The following section wants to be a
| |
− | dictionary of sorts, defining all the terms that you may come across when dealing with capabilities.
| |
− | | |
− | In the rest of this article, we will refer to these terms frequently, so make sure you are familiar with them.
| |
− | | |
− | ; Capability
| |
− | : the ability to perform something. In-code this is represented by the <code>Capability</code> class.
| |
− | ; Capability Provider
| |
− | : something that is able to support capabilities and provides a mean of accessing them. In-code they are represented by implementations of <code>ICapabilityProvider</code>. There are multiple kinds of capability providers:
| |
− | :; Volatile Provider
| |
− | :: a provider that doesn't persist data to disk; once the provider ceases to exist for any number of reasons, all capability data gets deleted.
| |
− | :; Persistent Provider
| |
− | :: a provider that requires all capabilities to serialize data to disk, in order to persist data even across game restarts. They implement the <code>INBTSerializable</code> interface.
| |
− | :; Agnostic Provider
| |
− | :: a provider that isn't neither volatile nor persistent, rather delegates the decision either to the capability directly or to sub-implementations. They also implement the <code>INBTSerializable</code> interface.
| |
− | ; Capability Interface
| |
− | : the interface that defines the contract of the capability, so what operations the capability exposes.
| |
− | ; Capability Implementation
| |
− | : one of the possibly many implementations of the capability interface, that actually carries out the work; one of the various implementations may also be considered the '''default capability implementation'''.
| |
− | ; Capability Storage
| |
− | : the manager that handles loading and storing persistent capabilities data from and to disk, guaranteeing preservation of information; in-code this is represented by an implementation of the <code>Capability.IStorage</code> interface.
| |
− | | |
− | The wary reader may note that both ''persistent'' and ''agnostic'' providers are represented the same way in code. In
| |
− | fact, the only difference between them comes from pure semantics in how their serialization methods are designed. This
| |
− | will be further discussed in their respective sections.
| |
− | | |
− | Moreover, it is also common to refer to the capability interface as simply the ''capability''. While not strictly
| |
− | correct, due to common usage we will also use this convention. So, to refer to the capability interface
| |
− | <code>MyCapability</code>, we will usually talk about the "<code>MyCapability</code> capability".
| |
− | | |
− | == Forge-provided Capabilities and Providers ==
| |
− | In order to ensure mods can work together seamlessly, Forge provides a set of default capabilities and capability
| |
− | providers.
| |
− | | |
− | The default capability providers in a Forge environment are: <code>TileEntity</code>, <code>Entity</code>,
| |
− | <code>ItemStack</code>, <code>World</code>, and <code>Chunk</code>. These are all agnostic providers, since they don't
| |
− | mandate any sort of capability persistency requirements. Rather, it is the job of whoever subclasses these providers to
| |
− | deal with either volatile or non-volatile capabilities.
| |
− | | |
− | The default capabilities that forge provides are represented by the interfaces <code>IItemHandler</code>,
| |
− | <code>IFluidHandler</code>, <code>IFluidHandlerItem</code>, <code>IEnergyStorage</code>, and
| |
− | <code>IAnimationStateMachine</code>. Each one of these capabilities will be discussed in the corresponding section.
| |
− | | |
− | === <tt>IItemHandler</tt> ===
| |
− | | |
− | The <code>IItemHandler</code> capability refers to the ability for any capability provider to have some sort of internal
| |
− | '''inventory''' with a certain number of slots, from which items can be inserted and extracted. It is also possible,
| |
− | though, to expose this capability even if no such inventory is present as long as the capability provider can emulate
| |
− | its presence (e.g. tools that allow accessing remote inventories).
| |
− | | |
− | This effectively '''replaces''' the vanilla interfaces <code>IInventory</code> and <code>ISidedInventory</code>. These
| |
− | interfaces are in fact retained only to allow vanilla code to compile and should not be used in mod code. This extends
| |
− | to anything that implements those vanilla interfaces, such as <code>LockableLootTileEntity</code>.
| |
− | | |
− | A default reference implementation for this capability interface is provided in <code>ItemStackHandler</code>.
| |
− | | |
− | === <tt>IFluidHandler</tt> ===
| |
− | | |
− | The <code>IFluidHandler</code> capability refers to the ability for any capability provider to handle and store fluids
| |
− | in one or multiple fluid tanks. It is effectively the equivalent in terms of fluids of the <code>IItemHandler</code>
| |
− | capability.
| |
− | | |
− | A default reference implementation for this capability interface is provided in <code>TileFluidHandler</code>.
| |
− | | |
− | === <tt>IFluidHandlerItem</tt> ===
| |
− | | |
− | The <code>IFluidHandlerItem</code> capability referes to the ability for an <code>ItemStack</code> capability provider
| |
− | to handle and store fluids in one or multiple fluid tanks. It is basically a specialized version of the
| |
− | <code>IFluidHandler</code> capability that allows <code>ItemStack</code>s to define a custom container.
| |
− | | |
− | === <tt>IEnergyStorage</tt> ===
| |
− | | |
− | The <code>IEnergyStorage</code> capability refers to the ability for any capability provider to store, consume, and
| |
− | produce energy. This capability is the base capability for what's commonly known in the modded world as Forge Energy (or
| |
− | FE), i.e. the energy system most mods use. Its internal design is heavily based on the (now defunct) Redstone Flux
| |
− | Energy API, supporting both a push and pull system.
| |
− | | |
− | A default reference implementation for this capability interface is provided in <code>EnergyStorage</code>.
| |
− | | |
− | === <tt>IAnimationStateMachine</tt> ===
| |
− | | |
− | The <code>IAnimationStateMachine</code> capability refers to the ability for any capability provider to leverage the
| |
− | Forge Animation State Machine API for animations.
| |
− | | |
− | == Working with Capabilities ==
| |
− | | |
− | Both capability providers and users need to be able to provide and access capabilities through a common framework,
| |
− | otherwise the ideal of dynamic and mod-agnostic would not really exist. For this reason, both capability providers and
| |
− | capability ''accessors'' (which we define as everything that wants to access a capability), also known as '''clients''' or '''users''',
| |
− | need to work together and with Forge to ensure that the common interface is used sensibly and correctly by all parties.
| |
− | | |
− | === Obtaining a Capability ===
| |
− | | |
− | Before being able to work with a capability, it is necessary to obtain an instance of the <code>Capability</code> object
| |
− | itself. Since these objects are created by Forge and there is only '''one''' unique instance for each capability that
| |
− | may exist, this instance cannot be obtained by "common" means. Forge provides two different methods of obtaining such
| |
− | instances: '''injecting''' into a field, or a '''callback method'''.
| |
− | | |
− | ==== Injecting into a Field ====
| |
− | | |
− | A <code>Capability</code> can be injected automatically into a field as soon as it gets created by Forge, following the
| |
− | principle commonly known as '''dependency injection'''. This provides less flexibility, since it doesn't notify the user
| |
− | that the capability has been injected nor runs arbitrary code. Nevertheless, it is '''suggested''' to use this method
| |
− | instead of the callback approach.
| |
− | | |
− | To inject the <code>Capability</code> into a field, all that's needed is to declare a <code>static</code> field of type
| |
− | <code>Capability<T></code>, where <code>T</code> represents the capability interface, and annotate it with
| |
− | <code>@CapabilityInject(T.class)</code>.
| |
− | | |
− | For a more practical example, consider the following snippet:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | @CapabilityInject(IItemHandler.class)
| |
− | public static Capability<IItemHandler> ITEM_HANDLER_CAPABILITY = null;
| |
− | </syntaxhighlight>
| |
− | | |
− | The above code will let Forge know that the field <code>ITEM_HANDLER_CAPABILITY</code> should be injected with the
| |
− | unique instance of the <code>IItemHandler</code> capability. Assigning the field to <code>null</code> allows us to
| |
− | provide a reasonable fallback in case the capability we want hasn't been registered yet.
| |
− | | |
− | This injection is, for obvious reasons, redundant, since that capability is also available through
| |
− | <code>CapabilityItemHandler</code>.
| |
− | | |
− | ==== Declaring a Callback ====
| |
− | | |
− | Another option is to declare a callback method, meaning a method that will be called with the value of the desired
| |
− | <code>Capability</code> once the instance is available. This gives more flexibility since the method may perform a
| |
− | number of arbitrary actions with the received instance prior to storing it in a field, or may even discard the
| |
− | capability entirely if wanted. Nevertheless, the usage of a field instead of a method is encouraged as a matter of
| |
− | style.
| |
− | | |
− | To use a method as a callback, the method must be declared as <code>static</code> and accepting a single parameter of
| |
− | type <code>Capability<T></code>, where <code>T</code> represents the capability interface. The method should also
| |
− | be annotated with <code>@CapabilityInject(T.class)</code>.
| |
− | | |
− | For a more practical example, consider the following snippet:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | public static Capability<IEnergyStorage> ENERGY = null;
| |
− | | |
− | @CapabilityInject(IEnergyStorage.class)
| |
− | private static void onEnergyStorageInit(Capability<IEnergyStorage> capability) {
| |
− | LOGGER.info("Received IEnergyStorage capability '{}': enabling Forge Energy support", capability);
| |
− | ENERGY = capability;
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | The above code declares a callback method that will be invoked when a <code>Capability</code> instance for
| |
− | <code>IEnergyStorage</code> is available. The callback then prints a log message and stores the capability into a
| |
− | <code>public</code> field for accessibility. The field is initialized to <code>null</code> to provide a reasonable
| |
− | fallback in case the capability does not exist.
| |
− | | |
− | This callback is, for obvious reasons, redundant, since that capability is also available through
| |
− | <code>CapabilityEnergy</code>.
| |
− | | |
− | === Exposing a Capability ===
| |
− | | |
− | Exposing a capability is a voluntary act by a capability provider that allows the capability to be discovered and
| |
− | accessed by users.
| |
− | | |
− | To do so, a capability provider needs to juggle a couple more moving pieces to ensure that the capability state remains
| |
− | consistent and that the lookup remains fast. It is in fact possible for a capability provider to be asked to provide
| |
− | many capabilities many times in the same tick. For this reason, a provider is asked to do the following:
| |
− | | |
− | * the <code>LazyOptional</code>s that get returned '''must be cached''';
| |
− | * if a capability changes exposure state (more on this later), all listeners '''must be notified''';
| |
− | * if a capability gets invalidated (more on this later), all listeners '''must be notified'''
| |
− | * the lookup inside <code>getCapability</code> must be performed with '''an <code>if</code>-<code>else</code> chain''';
| |
− | * all unexposed but still present capabilities '''should be available''' if the provider is queried with a <code>null</code> direction (see ''Accessing a Capability'' for more information);
| |
− | * if no capability of a given type is available or accessible, the provider '''must call <code>super</code> as long as it is possible to do so'''.
| |
− | | |
− | Capability providers must also reflect changes in the ''exposure state'' of a capability, meaning that if the
| |
− | accessibility of a capability from a certain <code>Direction</code> changes (refer to
| |
− | [[#Accessing a Capability|Accessing a Capability]] for more information), it is the provider's responsibility to trigger
| |
− | a state response by invalidating the returned <code>LazyOptional</code> and caching a new one. This should also be
| |
− | performed when a capability gets ''invalidated'', such as when a capability provider gets removed.
| |
− | | |
− | With all of the above in mind, part of a capability provider implementation may be similar to the following snippet:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | // suppose the presence of a field 'inventory' of type 'IItemHandler'
| |
− | | |
− | private final LazyOptional<IItemhandler> inventoryOptional = LazyOptional.of(() -> this.inventory);
| |
− | | |
− | @Override
| |
− | public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction direction) {
| |
− | if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY
| |
− | && (direction == null || direction == Direction.UP || direction == Direction.DOWN)) {
| |
− | return this.inventoryOptional.cast();
| |
− | }
| |
− | return super.getCapability(capability, direction); // See note after snippet
| |
− | }
| |
− | | |
− | @Override
| |
− | protected void invalidateCaps() {
| |
− | super.invalidateCaps();
| |
− | this.inventoryOptional.invalidate();
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | This possible implementation of a capability provider exposes an <code>IItemHandler</code> capability and restricts
| |
− | access only to the <code>UP</code> and <code>DOWN</code> directions. If we assume this capability provider is a
| |
− | <code>TileEntity</code>, then we may also say that the inventory is only accessible from the top and the bottom of the
| |
− | block.
| |
− | | |
− | Moreover, the capability gets automatically invalidated when the provider gets invalidated. Assuming this is a
| |
− | <code>TileEntity</code>, this usually happens when the block gets removed from the world or unloaded due to distance.
| |
− | | |
− | The <code>super</code> call at the end of the <code>getCapability</code> method is extremely important, since it's what
| |
− | allows Attaching external Capabilities to capability providers. Nevertheless, it is not always possible to invoke
| |
− | <code>super</code>: in those cases, an empty <code>LazyOptional</code> should be returned.
| |
− | | |
− | === Attaching a Capability ===
| |
− | | |
− | Attaching a Capability is a process by which external agents "modify" a Capability Provider, making it expose additional
| |
− | capabilities other than the already available ones.
| |
− | | |
− | To do so, the '''attaching agent''' (which means the thing that wants to attach a capability to another provider) must
| |
− | listen to the <code>AttachCapabilitiesEvent<T></code>. The <code>T</code> in this case represents the capability
| |
− | provider you want to attach the capability to. Note that the type of <code>T</code> '''must''' be the base type of the
| |
− | capability provider, not a subclass. As an example, if you want to attach a capability to a <code>MyTileEntity</code>,
| |
− | which extends <code>TileEntity</code>, you'll have to listen to <code>AttachCapabilitiesEvent<TileEntity></code>,
| |
− | '''NOT''' to <code>AttachCapabilitiesEvent<MyTileEntity></code>, since the latter will never fire.
| |
− | | |
− | The attaching agent can use the provided methods <code>getObject</code>, <code>addCapability</code>, and
| |
− | <code>addListener</code> to check whether the capability should be attached to the current object and perform the
| |
− | desired action.
| |
− | | |
− | When attaching a capability, the attaching agent should also provide a name in the form of a
| |
− | <code>ResourceLocation</code>. The name '''must''' be under the attaching agent's namespace, but no restrictions are
| |
− | placed on the actual name, as long as it is unique inside the given namespace.
| |
− | | |
− | Maybe a little counter-intuitively, the process of attaching does not attach a capability nor a capability interface
| |
− | directly. Rather, the attaching agent should create its own implementation of a '''Capability Provider''' and attach it
| |
− | via the event. This is done so that the attaching agent can have control over when, how, and where its capabilities are
| |
− | exposed, instead of relying on the game itself deciding these parameters. For this reason, all considerations given in
| |
− | the [[#Exposing a Capability|Exposing a Capability]] section on how to correctly create a Capability Provider.
| |
− | | |
− | With the above in mind, part of an attaching agent may be similar to the following snippet of code:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | @SubscribeEvent
| |
− | public void onAttachingCapabilities(final AttachCapabilitiesEvent<TileEntity> event) {
| |
− | if (!(event.getObject() instanceof EnergyBasedTileEntity)) return;
| |
− | | |
− | EnergyStorage backend = new EnergyStorage(((EnergyBasedTileEntity) event.getObject()).capacity);
| |
− | LazyOptional<IEnergyStorage> optionalStorage = LazyOptional.of(() -> backend);
| |
− | | |
− | ICapabilityProvider provider = new ICapabilityProvider() {
| |
− | @Override
| |
− | public <T> LazyOptional<T> getCapability(Capability<T> cap, @Nullable Direction direction) {
| |
− | if (cap == CapabilityEnergy.ENERGY) {
| |
− | return optionalStorage.cast();
| |
− | }
| |
− | return LazyOptional.empty();
| |
− | }
| |
− | };
| |
− | | |
− | event.addCapability(new ResourceLocation("examplemod", "fe_compatibility"), provider);
| |
− | event.addListener(optionalStorage::invalidate);
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | This example implementation of an attaching agent attaches a <code>IEnergyStorage</code> capability to all
| |
− | <code>TileEntity</code> instance that are a subclass of <code>EnergyBasedTileEntity</code>. It also sets up the
| |
− | <code>LazyOptional</code> for invalidation if the parent capability provider gets invalidated.
| |
− | | |
− | Note also the call of <code>LazyOptional.empty()</code> rather than a <code>super</code>. This is needed because when
| |
− | attaching a capability, the parent capability provider isn't known. For this reason, it is necessary to return an empty
| |
− | <code>LazyOptional</code>. The game will then handle automatic merging of the various different providers into a single
| |
− | one.
| |
− | | |
− | The above example is one of a '''Volatile''' Capability Provider. On the other hand, mods may want to persist their
| |
− | data across sessions. In this case, they should attach a '''Persistent''' Capability Provider: this can be done either
| |
− | by implementing the <code>INBTSerializable</code> interface along with <code>ICapabilityProvider</code> or by
| |
− | implementing the <code>ICapabilitySerializable</code> interface.
| |
− | | |
− | The previous example reworked to use a Persistent Capability Provider may be similar to the following snippet:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | @SubscribeEvent
| |
− | public void onAttachingCapabilities(final AttachCapabilitiesEvent<TileEntity> event) {
| |
− | if (!(event.getObject() instanceof EnergyBasedTileEntity)) return;
| |
− | | |
− | EnergyStorage backend = new EnergyStorage(((EnergyBasedTileEntity) event.getObject()).capacity);
| |
− | LazyOptional<IEnergyStorage> optionalStorage = LazyOptional.of(() -> backend);
| |
− | Capability<IEnergyStorage> capability = CapabilityEnergy.ENERGY;
| |
− | | |
− | ICapabilityProvider provider = new ICapabilitySerializable<IntNBT>() {
| |
− | @Override
| |
− | public <T> LazyOptional<T> getCapability(Capability<T> cap, @Nullable Direction direction) {
| |
− | if (cap == capability) {
| |
− | return optionalStorage.cast();
| |
− | }
| |
− | return LazyOptional.empty();
| |
− | }
| |
− | | |
− | @Override
| |
− | public IntNBT serializeNBT() {
| |
− | return capability.getStorage().writeNbt(capability, backend, null);
| |
− | }
| |
− | | |
− | @Override
| |
− | public void deserializeNBT(IntNBT nbt) {
| |
− | capability.getStorage().readNBT(capability, backend, null, nbt);
| |
− | }
| |
− | };
| |
− | | |
− | event.addCapabilities(new ResourceLocation("examplemod", "fe_compatibility"), provider);
| |
− | event.addListener(optionalStorage::invalidate);
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | | |
− | === Accessing a Capability ===
| |
− | | |
− | Accessing a Capability is the process by which a user is able to '''query''' a Capability Provider for a specific
| |
− | instance of a capability.
| |
− | | |
− | This is perhaps the second most important part of the entire capability system, since it is what allows cross-mod
| |
− | interaction. To obtain an instance of a Capability, the user must first get a hold of the Capability Provider that
| |
− | should be queried. This can be done in a variety of ways and is outside the scope of this article. The user should
| |
− | then invoke the <code>getCapability</code> method passing the unique instance of the capability that should be queried
| |
− | (see [[#Obtaining a Capability|Obtaining a Capability]] for more information) and the querying <code>Direction</code>,
| |
− | if applicable.
| |
− | | |
− | The returned object is a <code>LazyOptional</code> wrapping the queried Capability, if the capability provider exposes
| |
− | it, otherwise it will be empty. The <code>LazyOptional</code> can be either unwrapped via an <code>orElse</code> or
| |
− | used directly via <code>ifPresent</code>.
| |
− | | |
− | It is '''highly suggested''' to cache the returned <code>LazyOptional</code> to avoid querying the same provider every
| |
− | time, in order to improve performance. The user should thus register itself to the invalidation listener via the
| |
− | <code>addListener</code> method. This ensures that the user will be able to react to the invalidation of the
| |
− | <code>LazyOptional</code> and remove it from the cache.
| |
− | | |
− | With the above in mind, part of an user may be similar to the following snippet of code:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | private final Map<Direction, LazyOptional<IEnergyStorage>> cache = new HashMap<>();
| |
− | | |
− | private void sendPowerTo(int power, Direction direction) {
| |
− | LazyOptional<IEnergyStorage> targetCapability = cache.get(direction);
| |
− | | |
− | if (targetCapability == null) {
| |
− | ICapabilityProvider provider = world.getTileEntity(pos.offset(direction));
| |
− | targetCapability = provider.getCapability(CapabilityEnergy.ENERGY, direction.getOpposite());
| |
− | cache.put(direction, targetCapability);
| |
− | targetCapability.addListener(self -> cache.put(direction, null));
| |
− | }
| |
− | | |
− | targetCapability.ifPresent(storage -> storage.receiveEnergy(power, false));
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | This example implementation of an user is querying via a <code>TileEntity</code> the neighboring capability provider
| |
− | for an <code>IEnergyStorage</code> capability. Before obtaining the provider, the code performs a cache lookup for the
| |
− | targeted capability. If the check succeeds, then no lookup is performed; if the check fails, the targeted Capability
| |
− | Provider is obtained and queried for the Capability. The obtained <code>LazyOptional</code> is then cached and a
| |
− | listener is attached to it so that the cache would be emptied on invalidation. The code then continues with the
| |
− | interaction with the capability, which is outside the scope of this article.
| |
− | | |
− | == Creating Custom Capabilities ==
| |
− | | |
− | While the various capabilities provided by Forge may satisfy the most common use cases, there is always the chance that
| |
− | a mod may require a custom solution. For this reason, Forge provides a way to define a custom Capability.
| |
− | | |
− | Defining a custom Capability requires the user to provide three main components: the Capability Interface, at least one
| |
− | Capability Implementation, and the Capability Storage. Optionally, a Capability Provider can also be created. In this
| |
− | case, the provider will be used as described in [[#Attaching a Capability|Attaching a Capability]]. The various details
| |
− | for all these components are described in the respective sections of this article.
| |
− | | |
− | In this section, we will refer to the implementation of a <code>MyCapability</code> capability, that can be used to
| |
− | store a single mutable <code>String</code>.
| |
− | | |
− | Refer also to [[#Code Examples|Code Examples]] for an example on how the various components may be implemented in a
| |
− | real-world scenario.
| |
− | | |
− | === The Capability Interface and the Capability Implementation ===
| |
− | | |
− | The Capability Interface is one of the most important parts of a Capability: without it, the Capability effectively does
| |
− | not exist. Designing a Capability Interface is exactly like designing any Java interface, so the particular details will
| |
− | be glossed over in this section.
| |
− | | |
− | The Capability Implementation, on the other hand, is the implementation of the previously defined Capability Interface.
| |
− | Usual rules for interface implementations follow. There can be more than one Capability Implementation for each
| |
− | capability, but no less than one.
| |
− | | |
− | Note that a '''well-formed''' capability implementation should '''not store''' the Capability Provider inside of it: we
| |
− | call the capability implementation ''provider-agnostic''. This is not a hard-requirement, though, rather it should act
| |
− | more as a guideline. There are in fact certain situations where this cannot be avoided (e.g. attaching a client-synced
| |
− | capability to an <code>ItemStack</code>).
| |
− | | |
− | One of the various Capability Implementation should also act as the default implementation. Other mods can ask the
| |
− | capability to create an instance of the default implementation without ever referring to such an implementation
| |
− | themselves. This guarantees separation of API code from implementation code, which is also one of the goals of the
| |
− | capability system.
| |
− | | |
− | Given all of the above information, this may be an example implementation of both a Capability Interface and a
| |
− | Capability Implementation:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | public interface MyCapability {
| |
− | String getValue();
| |
− | void setValue(String value);
| |
− | }
| |
− | | |
− | public class MyCapabilityImplementation implements MyCapability {
| |
− | private String value;
| |
− | | |
− | @Override
| |
− | public String getValue() {
| |
− | return this.value;
| |
− | }
| |
− | | |
− | @Override
| |
− | public void setValue(String value) {
| |
− | this.value = value;
| |
− | }
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | Note that in this case, only a single implementation is provided, which will also act as the default implementation for
| |
− | the <code>MyCapability</code> capability.
| |
− | | |
− | === The Capability Storage ===
| |
− | | |
− | The Capability Storage is that component of the Capability System that is responsible for serializing and deserializing
| |
− | a capability. All capabilities must provide one, since certain providers may or may not require that their capabilities
| |
− | are serializable.
| |
− | | |
− | The Capability Storage implements the <code>Capability.IStorage<T></code> interface, where <code>T</code> is the
| |
− | generic type of the Capability Interface the storage refers to. Each capability must have '''one and exactly one'''
| |
− | Capability Storage.
| |
− | | |
− | The Storage is usually called by the Capability Provider when serialization or deserialization needs to happen. The
| |
− | Storage is then responsible of reading the data from the given capability instance and convert that into an NBT-based
| |
− | structure that can be serialized. A Storage may also return <code>null</code> to indicate that no serialization is
| |
− | necessary, although some providers may require an empty tag to be supplied instead. At the same time, the Storage is
| |
− | also responsible for restoring the original state of the capability when deserialization happens. In this case, the
| |
− | given NBT structure is guaranteed not to be <code>null</code>.
| |
− | | |
− | In all cases, a <code>Direction</code> is provided for context, if available.
| |
− | | |
− | Although discouraged as a matter of code cleanliness, it is legal for a Capability Storage to require a specific
| |
− | Capability Implementation for the serialization and deserialization to be successful. If this is the case, this
| |
− | requirement '''must''' be documented, though the code should be refactored wherever possible to remove this requirement.
| |
− | | |
− | Given the above information, an example of an implementation of a Capability Storage may be the following:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | public class MyCapabilityStorage implements Capability.IStorage<MyCapability> {
| |
− | @Override
| |
− | @Nullable
| |
− | INBT writeNBT(Capability<MyCapability> capability, MyCapability instance, Direction direction) {
| |
− | return StringNBT.valueOf(instance.getValue());
| |
− | }
| |
− | | |
− | @Override
| |
− | public void readNBT(Capability<MyCapability> capability, MyCapability instance, Direction direction, INBT nbtData) {
| |
− | if (!(nbtData instanceof StringNBT)) {
| |
− | throw new IllegalArgumentException("Unable to deserialize 'MyCapability' from a non-String NBT structure");
| |
− | }
| |
− | instance.setValue(((StringNBT) nbtData).getString());
| |
− | }
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | Note the <code>instanceof</code> check needed to ensure that the given <code>INBT</code> instance is valid for
| |
− | deserialization. A Capability Storage should always perform this check prior to the cast in order to provide a
| |
− | meaningful error message, rather than a cryptic <code>ClassCastException</code>.
| |
− | | |
− | === The Capability Provider ===
| |
− | | |
− | The Capability Provider is an optional component of the capability that allows the Capability to be attached to a
| |
− | component. The details on how a Capability Provider should behave have already been discussed in the two previous
| |
− | sections [[#Exposing a Capability|Exposing a Capability]] and [[#Attaching a Capability|Attaching a Capability]]: refer
| |
− | to those for more information.
| |
− | | |
− | === Tying it All Together ===
| |
− | | |
− | Once all components of a Capability have been created, they must be registered so that the game is aware of the
| |
− | capability's presence. The registration requires specifying the Capability Interface, the Capability Storage, and a
| |
− | factory for the default capability implementation.
| |
− | | |
− | The registration can be performed by calling the <code>register</code> method on the <code>CapabilityManager</code>.
| |
− | This '''needs''' to happen when the <code>FMLCommonSetupEvent</code> is fired on the <code>MOD</code> event bus. The
| |
− | registration will also automatically inject the created Capability into all relevant fields and methods: refer to
| |
− | [[#Obtaining a Capability|Obtaining a Capability]] for more information.
| |
− | | |
− | An example of registration can be found in the snippet that follows:
| |
− | | |
− | <syntaxhighlight lang="java">
| |
− | public void onCommonSetup(FMLCommonSetupEvent event) {
| |
− | CapabilityManager.INSTANCE.register(MyCapability.class, new MyCapabilityStorage(), MyCapabilityImplementation::new);
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | == Custom Capability Providers ==
| |
− | | |
− | Much like custom Capabilities, Forge also allows the creation of custom Capability Providers. The main advantage of this
| |
− | is allowing mods to create custom providers for their custom objects, in order to promote not only cross-mod
| |
− | compatibility, but also uniformity in the way users may interact with different mod APIs.
| |
− | | |
− | This section will only give the basic outline of what is necessary to implement a custom Capability Provider: for more
| |
− | in-depth explanation, people are referred to the game code.
| |
− | | |
− | By definition, a custom Capability Provider is everything that implements the <code>ICapabilityProvider</code>
| |
− | interface. In this section, though, we will only cover people that may want to replicate the functionality of one of
| |
− | the default providers, such as <code>TileEntity</code> or <code>Chunk</code>.
| |
− | | |
− | The easiest way of doing this is extending the <code>CapabilityProvider</code> class provided by Forge. This will
| |
− | automatically set up an ''agnostic'' Capability Provider. To fully initialize the capability provider, the subclass
| |
− | should then invoke the <code>gatherCapabilities</code> method as the last instruction in its constructor, to ensure that
| |
− | the game is able to recollect and attach all capabilities that other mods may want to attach to the capability provider.
| |
− | | |
− | == Code Examples ==
| |
− | | |
− | * [https://gist.github.com/TheSilkMiner/5cc92ba573e7bdd871dfdbffdd5c2806 A Gist showing a quick and dirty example on how to implement a Capability effectively]
| |