Minecraft Modding: Capabilities

Background


Entity sub-classes save and load information using the writeToNBT() and readFromNBT() methods. For custom entities you have the ability to directly @Override these methods, so that's all you need to do.

Warning: The Entity class actually has some other similarly-named methods called writeEntityToNBT() and readEntityFromNBT() which are different -- they include a bunch of other information about the entity like UUID that are not typically needed.

However, if you need to add information for vanilla entities you won't be able to directly override these methods. So to allow this (adding information to entities):
  • in 1.9 and later, you should use the Capability system.
  • in 1.8 and earlier there is an interface provided called  IExtendedEntityProperties  that helps handle these properties.  See Jabelar's Extended Entity Properties Tutorial.
I explain how to use the Capability system here.

Note: If the information you want to add to the vanilla entity isn't changeable, you can instead use the IEntityAdditionalSpawnData interface. This is available in all versions of Forge.

Warning: Both IEEP and Capability are not automatically synced between client and server, so if you need that then you will want to add a custom packet whenever the value changes to sync.

References To Other Information On Capabilities


You should also check out:


Converting IEEP To Capabilities


If you're upgrading your mod you may need to convert your IEEP to Capabilities. The official Forge documentation explains how to convert.


Capabilities (Version 1.9 and Later)


Capabilities can be attached to entities, tile entities, items and worlds.

The concept of a capability is that you need to both register and "attach" the capability. Registration happens in your pre-init handling code using the CapabilityManager.INSTANCE.register() method. Attaching the capability depends on whether you're attaching to your own class (in which case you @Override the hasCapability() and getCapability() methods) or you handle the AttachCapabilitiesEvent.

You can attach existing capabilities, like IItemHolder or create your own custom one (e.g. for "mana" level).

So there are actually four different cases where you might use capabilities, and they are actually fairly different in terms of what you need to do:

  1. Using existing capabilities (e.g. IItemHolder) in your custom classes
  2. Using custom capabilities in your custom classes
  3. Using existing capabilities on vanilla (or other mod's) classes
  4. Using custom capabilities on vanilla (or other mod's) classes

The rest of my tutorial covers the most complicated case, Case #4, of making custom capabilities and attaching them to vanilla classes.

Attaching Custom Capabilities To Vanilla Classes


Here are the steps for using creating custom capabilities and using them on vanilla classes:
  1. Create a Capability interface (e.g. IMana) for each capability that has a getter and setter method for that capability. This class does not need to extend or implement anything and the getter and setter methods can have any name you wish (e.g. setMana() and getMana()).
  2. Create a class (e.g. Mana) that implements your capability interface. Typically this simply has a private field that holds the data and @Overrides the interface getter and setter methods.
  3. Create a class (e.g. ManaStorage) that implements IStorage that writes and reads classes that implement your capability interface(s). You will have to @Override the writeNBT() and readNBT() methods. To write you need to create a NBTTagCompound (or related NBTBase extension) and copy the capability data into it. To read you will expect to get the same format NBTTagCompound and simply have to read out the data in the reverse order.
  4. Create a provider class (e.g. ManaProvider) that implements ICapabilitySerializable and furthermore:
    • Create a constant field that is declared as type of your capability interface (e.g. public final static Capability MANA = null;) and "inject" your capability to it by adding the @CapabilityInject annotation.
    • @Override the hasCapability() and getCapability() methods to provide the constant field.
    • @Override the serializeNBT() and deserializeNBT() methods to use the storage (e.g. MANA.getStorage()) to call the related writeNBT() and readNBT() methods.
  5. Register your capability with the CapabilityManager.INSTANCE.register() method from your "pre-init" handler method of your common proxy. E.g. CapabilityManager.INSTANCE.register(IMana.class, new ManaStorage(), Mana.class).
  6. If you're attaching the capability to a vanilla class, like EntityPlayer you need to handle the AttachCapabilitiesEvent by checking the entity type and using the event.addCapability() method. In this method you're supposed to give a name key (e.g. new ResourceLocation(myModID, "Mana")) but the actual name doesn't seem to be too important as you will interact directly with the instance.

Key Point: To use your capability in your code, you simply use the getCapability() method on the appropriate thing and further call the getter and setter methods as appropriate (e.g. if you attached the capability to a player you'd use player.getCapability().getMana()).

Tip: You can attach multiple different capabilities to a capability provider.
Important: Capabilities are not automatically synced between server and client. So you may need to implement a networking solution to pass the information.

Warning: For player entities, capabilities attached to them will not persist across death and respawn. To copy them, you will need to handle the PlayerEvent.Clone event. For example, something like this in the event handler:

   // clone capabilities
   EntityPlayer player = event.getEntityPlayer();
   IMana mana = player.getCapability(ManaProvider.MANA, null);
   IMana oldMana = event.getOriginal().getCapability(ManaProvider.MANA, null);
   mana.setMana(oldMana.getMana());

Syncing ItemStack Capabilities To Client


It is a fairly common modding desire to do some sort of rendering or client side logic based on capability data. However, as mentioned above, the capability data is not automatically synced between server and client.

It is possible to solve this in multiple ways, but all of them will require some sort of networking. For ItemStack syncing I recommend using the Item#getNBTShareTag() method to append the capability data that needs to sync to client.

The recommended steps are:

  1. Make sure your custom Capability IStorage properly implements a readNBT() and writeNBT() methods.
  2. In your custom Item class make sure that the getShareTag() returns true and that the getNBTShareTag() method adds the cability NBT (which you can get from Capability#writeNBT()). Important: You will want to specify some sort of key that you can use on client side to extract the  tag data.
  3.  In your client rendering or wherever you need it, you just look in the ItemStack NBT for the key that you specified in Step 2 above. 
  4. Optionally. You can sync the entire client-side capability, however for most cases I think you can use the NBT data. 


Conclusion


The Capability  system is very important interfaces when changing the behavior of vanilla entities. It is worth your time to make sure you understand these useful tools.

4 comments:

  1. Hi, thanks for the tutorial. Is there a way to save the capability information that we attached to a chunk even if the game is closed?

    ReplyDelete
  2. Thanks for the tutorial. How would I go about spawning a mob that is on fire? I'm creating a Fire Zombie that spawns in the Nether. I want it either A) Spawn already on fire, or B) Spawn and find the nearest fire/lava and essentially light itself on fire. When on fire, it gets a stat boost and a regen factor. If I decide I want it to spawn in the overworld, I'll want it to avoid water (as it will have regular zombie stats when not on fire, but still be drawn to fire if not already.

    ReplyDelete
  3. Good coding background information about minecraft mods.

    For more information

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete