Another award-winning primer by williewillus
Capabilities...a wondrous new system. That you've probably been forced into using. But let's not talk about that and get straight into the learning!
- Capability System - This entire system; what this primer is about. This system is named very literally for what it does.
- Capability - the quality of being capable; capacity; ability
- Capable - having power and ability
- Capability Interface - Some class (doesn't need to be an interface but highly recommended) that represents the functionality you want to expose. Example: Look up
IItemHandler
in your IDE, another example - Capability Implementation - Any implementation of a capability interface. Unique instances of this are attached to Entities/ItemStacks/etc.
- Capability Object - An object that is a subclass of
Capability<T>
whereT
is a Capability Interface. It serves as a "blueprint" of sorts for operations pertaining to your capability interface. It's sort of likeBlock
, which doesn't represent a block in the world, rather a blueprint for all blocks of that type in the world. INBTSerializable<T extends NBTBase>
- Marker interface for anything that can be written and read from NBT.ICapabilityProvider
- Interface that states that the implementing object can hold and provide capability implementations to those who request them. THIS IS NOT YOUR CAPABILITY INTERFACE.- Provides methods
hasCapability
andgetCapability
- These both take a Capability Object and a EnumFacing side
- The first returns boolean
- The second returns an object with the type of the Capability Interface
T
(the sameT
as the one in the passed Capability ObjectCapability<T>
) - Notable: In vanilla TileEntity, ItemStack, Entity, World, and Chunk implement this interface.
- Provides methods
ICapabilitySerializable<T>
- A convenience combination of the above two interfaces
- Entities, TileEntities, and ItemStacks all delegate their
hasCapability
andgetCapability
methods to an instance of this class. CapabilityDispatcher
, simply put, is just anICapabilityProvider
that holds otherICapabilityProvider
s. These "child" providers are identified by aResourceLocation
name.- When asked for a Capability Implementation, the dispatcher simply asks all of its children, in order, until it finds one.
CapabilityDispatcher
implementsINBTSerializable
. When written or read, the dispatcher simply calls the corresponding read and write methods on all of its children that implementINBTSerializable
themselves.CapabilityDispatcher
s can and DO hold OTHERCapabilityDispatcher
s. When reading or writing from NBT or when asking for Capability Implementations, a dispatcher always queries its parent first, if one exists.
Note that this entire section is only applicable if you are creating your own new Capabilities.
Simply call CapabilityManager.INSTANCE.register()
It wants three things:
- The class of your Capability Interface
- An instance of
IStorage
(wha??? hang in there, I'll explain) - The class of one Capability Implementation (or a
Callable
factory) that you want to be the default implementation of this Capability Interface. For most intents and purposes, this will be the * only * implementation of the interface. Then, the internal manager will generate a Capability Object representing the Capability we just registered.
- Whenever a Capability Object is created, Forge will look for all fields with the annotation
@CapabilityInject(SomeClass.class)
. IfSomeClass
matches the class of your Capability Interface, Forge will then insert the Capability Object into that field. The field must be static and have typeCapability<SomeClass>
. - Forge will also look for all methods containing the same annotation. When the Capability Object is created, that method will be called, passing the Capability Object as a parameters. The method must be static, and take one parameter of type
Capability<?>
- You can do this with the Capability Interfaces of other APIs, thus allowing a powerful and safe way for testing if some Capability Object is present within your game environment, or enabling features whenever a Capability is registered.
- Because of how the JVM processes annotations you can mention interfaces inside @CapabilityInject that could potentially be absent at runtime! The annotation just gets ignored.
- An
IStorage
is a public-facing utility object that is able to read and write your Capability Interface to and from NBT. - "Wait a minute!" you say, confused that we already discussed NBT above and now here comes something that seems completely redundant.
- An
IStorage
is simply a general purpose object that knows, for sure, how to read and write the default Capability Implementation. It may also be above to read and write other implementations, depending on the API author. - This is mainly useful for Capability Interfaces that are meant to be exposed and used publicly, such as energy or inventory APIs. In fact,
IItemHandler
(the Forge-provided Capability Interface for inventories) gives you a fully functionalIStorage
that is able to write and read. Thus, no one should ever need to write their own serialization/deserialization code for anIItemHandler
. - If your Capability Interface is merely for internal usage, feel free to return an
IStorage
that does nothing - returns null in the write method and does nothing in the read method. Or, you could implement it but only use it internally. Your personal choice.
- If you ever want to attach a capability to a "foreign" object (i.e. you do not have that object's code where you could sit down and edit it freely), then you must use
AttachCapabilitiesEvent
- This event is like any other Forge event. I assume you have knowledge about that system.
- This event has many specializations: for
TileEntity
,Entity
,ItemStack
,World
,Chunk
, etc. - Add a capability by calling
event.addCapability
. It wants two things:- A unique
ResourceLocation
name for your capability - An
ICapabilityProvider
- A unique
- This might sound familiar and you'd be right if you think so! It turns out, after the event is complete, all foreign
ICapabilityProvider
s gathered are neatly tucked into theCapabilityDispatcher
at the top. - Remember that if your
ICapabilityProvider
implementsINBTSerializable
, thenCapabilityDispatcher
will automatically call its read and write functions to load and save it from disk for you! - If you don't want your cap to saved (e.g. your information is volatile and recreated each time the world is loaded), then don't implement
INBTSerializable
. - See below for information about writing
ICapabilityProvider
s
- Item capabilities work much in a similar way as foreign capability attachment.
- Override
Item.initCapabilities
. - You are passed the ItemStack and the capability NBT subtag of the old stack, and are expected to return an
ICapabilityProvider
as before. - Your
ICapabilityProvider
becomes the top level capability provider of the ItemStack, theCapabilityDispatcher
mentioned far above becomes its child. - See below for information about writing
ICapabilityProvider
s
- You simply need to override
hasCapability
andgetCapability
, as the two classes mentioned all implementICapabilityProvider
. You do NOT need to supply your ownICapabilityProvider
like in the above cases. - As you are in control, you need to save your Capability Implementations yourself. Use the provided
IStorage
if applicable. - WARNING: You must fall back to super whenever you do this. Intuitively, from what we've learned so far, this makes sense. "Foreign" Capability Implementations are attached as children inside the
CapabilityDispatcher
wayyyy up in the class hierarchy inEntity
andTileEntity
. If you do not call super if you don't find your own Capability Object then you basically hide all other Capabilities further up in the inheritance chain and all other Capabilities attached by other people. - WARNING 2: Make these methods FAST. They may potentially get called every tick and capabilities need to react quickly. In most cases you can use direct if-statements like below. Try not to use data structures unless ABSOLUTELY needed.
- An example (names use the terminology we've been using so far):
private final MyCapInterface myImpl = new MyCapImplementation();
@CapabilityInject(MyCapInterface.class)
public static Capability<MyCapInterface> MY_CAP_OBJECT = null; // This would probably be somewhere else
@Override
public static boolean hasCapability (Capability<?> capObject, EnumFacing side) {
if (capObject == MY_CAP_OBJECT && side == EnumFacing.UP) {
return true;
} else {
return super.hasCapability(capObject, side);
}
}
@Override
public static <T> T getCapability(Capability<T> capObject, EnumFacing side) {
if (capObject == MY_CAP_OBJECT && side == EnumFacing.UP) {
return MY_CAP_OBJECT.cast(myImpl);
} else {
return super.getCapability(capObject, side);
}
}
- It might be a bit confusing what goes in here at first, but it gets more intuitive after an example
- The Provider simply holds an instance of the Capability Implementation to give to whoever wants it. That should be it.
- Although the behaviour shown in the example is simple, if you need complex behaviour you can pass the Entity, TileEntity, or Item directly into the Provider to operate on it directly. Be mindful of memory leaks.
- Remember that making it
INBTSerializable
lets it get saved automatically. In this case, I simply defer to the Capability Interface, which I made extendINBTSerializable
as well. - Remember to make the two methods fast!
- Obtain the Capability Object using
@CapabilityInject(<CapabilityInterface>.class)
- In some cases, a public field is already available in the API for usage.
- For example a copy of
IItemHandler
's Capability Object lives atCapabilityItemHandler.ITEM_HANDLER_CAPABILITY
- Simply call
getCapability
and pass the Capability Object you want and the side you want to query. Null is a VALID side to ask about! You will receive back either an instance of the Capability Interface or null if nothing was found.
IItemHandler
is the Forge-provided Capability Interface for all inventories. It is meant to replace usages ofIInventory
.ItemStackHandler
is the default Capability Implementation- All vanilla TE's are retrofitted to expose this Capability Interface
- Several entities also expose this Capability Interface (Horses, Mobs, and Players)
ItemHandlerHelper
is a helper class provided by Forge to deal withIItemHandler
s. It is SUPER useful, and with it you can get rid of all of your potentially buggy inventory insertion and extraction boilerplate- You can use
IItemHandler
in Containers as well, just useSlotItemHandler
which takes anIItemHandler
instead ofIInventory
- Important note: If you have item handlers that restrict automated insertion and extraction by guarding insertItem/extractItem, it's easy to accidentally break your slots since
SlotItemHandler
usesinsertItem
andextractItem
to put things in the slot. I advise, in your internal code, pass around a Capability Implementation that has less restriction so that GUIs work properly. Then, ingetCapability
return a wrapped Implementation that restricts insertion and extraction as appropriate
- Important note: If you have item handlers that restrict automated insertion and extraction by guarding insertItem/extractItem, it's easy to accidentally break your slots since
No. Most of it should still work, but the major thing that's changed in modern versions is a LazyOptional is now returned instead of having separate
has/getCapability
methods