Skip to content

Instantly share code, notes, and snippets.

@tterrag1098
Created February 5, 2017 07:49
Show Gist options
  • Save tterrag1098/11069f9f9db6b15878c7f938e6f55531 to your computer and use it in GitHub Desktop.
Save tterrag1098/11069f9f9db6b15878c7f938e6f55531 to your computer and use it in GitHub Desktop.

The Capability System

A capability can be thought of much like a trait. It allows exposing features in a dynamic and flexible way, without having to resort to directly implementing many interfaces.

In general terms, a capability is a marker object (generally a singleton) which provides an implementation of the capability, specific to the parent object. For instance, in the case of a chest with an inventory, we have:

  • The parent object - this is the Tile Entity, ItemStack, Entity, or whatever it may be that "owns" the capability. In this case, it is the chest Tile Entity.

  • The capability - the "idea" of an inventory can be considered the capability. A capability is not any one implementation of an inventory, but more of a contract stating whether the parent object has the ability (or capability) to be treated as an inventory.

  • The implementation - which is the actual code that handles the inventory logic. In the past, this would have simply been the Tile Entity which had methods inside it for this purpose. Now, a special purpose object handles this logic.

Capabilities may provide a default implementation, but it is not required. Additionally, capabilities can define a storage handler for at least this default implementation. The storage handler can support other implementations, but this is up to the capability implementor, so look it up in their documentation before trying to use the default storage with non-default implementations.

Forge adds capability support to TileEntities, Entities, and ItemStacks, which can be exposed either by attaching them through an event or by overriding the capability methods in your own implementations of the objects. This will be explained in more detail in the following sections.

Why Capabilities?

You may be asking yourself "Why is this system better?" While it's true that capabilities are more complex than simple interfaces, they provide a few key benefits that make them an improvement.

Separation of Concerns

Capabilities allow you to separate implementation of a feature from game objects. For instance, inventory code is now separated from tile entities. This allows you to better reuse code across different tile entities, instead of reimplementing inventory logic.

Easier Compatibility

Gone are the days of @Optional, with capabilities it is easier than ever to support other mods. With the use of @CapabilityInject, it is easy to tell if a mod is present or not. If not, the capability simply will not exist! This results in a query for a null capability, which will simply return false from hasCapability.

Hopefully it is obvious why capabilities are now the preferred option for many systems in both forge and other mods.

Forge-provided Capabilities

Forge currently provides three capabilities:

  • IItemHandler

    • Used for handling inventory slots. This is not only used for TileEntities, but Entities (extra player slots, mob/creature inventories/bags) and ItemStacks (portable backpacks and such) as well. It replaces the old IInventory and ISidedInventory with an automation-friendly system.
  • IFluidHandler

    • Represents a fluid containing object. It replaces the old IFluidHandler with a more consistent and automation-friendly system.
  • IEnergyStorage

    • Represents an energy containing object. It is based on the RedstoneFlux API by TeamCoFH, but modified to make sense as a capability.

Using a Capability

Obtaining a Capability Instance

In order to obtain a capability, you will need to refer it by its unique instance. For instance, the item handler capability is primarily stored in CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, but it is possible to get another reference by using the @CapabilityInject annotation.

@CapabilityInject(IItemHandler.class)
static Capability<IItemHandler> ITEM_HANDLER_CAPABILITY = null;

When the annotation is applied to a field, it will assign the instance of the capability (the same one gets assigned to all fields) upon registration of the capability, and left to the existing value (null), if the capability was never registered. Because local static field accesses are fast, it is a good idea to keep your own local copy of the reference for objects that work with capabilities.

Additionally, this annotation can also be used on a method, in order to get notified when a capability is registered, so that certain features can be enabled conditionally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment