Minecraft Modding: Capabilities and Extended Entity Properties

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 can no longer use IExtendedEntityProperties but rather should use Capabilities.
  • in 1.8 and earlier there is an interface provided called  IExtendedEntityProperties  that helps handle these properties.  
I explain how to use these approaches 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.

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)


Here are the steps for using capabilities:
  1. Create a Capability interface for each capability that has a getter and setter method for that capability.
  2. Create a class that extends IStorage that writes and reads classes that implement your capability interface(s).
  3. Create a class that extends your capability  interface.
  4. "Inject" your capability interface class into your main mod class.
  5. Create a final static capabilities constant that implements your capability interface in your main mod class.
  6. Register your capability in the capability class from your "pre-init" handler method of your common proxy.
  7. If you're attaching the capability to a vanilla class, like EntityPlayer you need to handle the AttachCapabilitiesEvent add your capabilities by overriding all the related methods. If you're attaching the capability to your own custom class you can @Override the methods related to capabilities in your class.
  8. Use getCapability() to access the capability where needed.

For more information refer to the official documentation on capabilities. Also there are some example implementations such as:


IExtendedEntityProperties (Version 1.8 and Earlier)


You may want to check out CoolAlias' tutorial on this subject, as he has interesting example where he uses it to add custom properties to the EntityPlayer.

To summarize, you use IExtendedEntityProperties if: 
  • You want to add data to a vanilla entity, and
  • The data may change during the game.
Warning: If you're using IEEP for players, be aware that every time a player re-spawns or travels to another dimension, technically it is a new instance of the player object. (The UUID will remain the same, but that isn't relevant to IEEP.) So it means that the loading of the IEEP won't persist if the player re-spawns or changes dimension. To solve this (thanks to coolalias for this tip):
  • Subscribe to PlayerEvent.Clone and copy the IEEP data to the new player instance's IEEP.
  • There should also be a class field from the event that tells you whether the player died or is simply re-spawning due to dimensional travel.
There are two steps to using IExtendedEntityProperties:
  1. Create a class that implements the interface.
  2. Register the class in your onEntityConstructing event handler.

Step 1. Create A Class That Implements IExtendedEntityProperties


In that class, create protected fields for the entity and the world. Eclipse should give you a warning about unimplemented methods, so accept its suggested fix to create those methods for your.
In the init() method copy the entity and world parameters to the associated fields. In the saveNBTData() method, use the compound.setxxx() type methods (where xxx should be replaced with the data type) to take each entity field getter and store it in NBT tag of similar name. In the loadNBTData() method, use the entity’s setter methods and grab the compound.getxxx() methods (where xxx should be replaced with the data type) to retrieve each tagged data.

Example:

public class ExtendedPropertiesHerdAnimal implements IExtendedEntityProperties
{
    public final static String extendedPropertiesName = "extendedPropertiesWildAnimal";
    protected EntityHerdAnimal theEntity;
    protected World theWorld;

    @Override
    public void saveNBTData(NBTTagCompound parCompound)
    {
       // DEBUG
       System.out.println("ExtendedPropertiesHerdAnimal saveNBTData()");

       // good idea to keep your extended properties in a sub-compound to 
       // avoid conflicts with other possible extended properties,
       // even from other mods (like if a mod extends all EntityAnimal)

        NBTTagCompound compound = new NBTTagCompound();
        parCompound.setTag(extendedPropertiesName, compound); // set as a sub-compound
        compound.setFloat("scaleFactor", theEntity.getScaleFactor())
        compound.setInteger("rearingCounter", theEntity.getRearingCounter());
        compound.setInteger("rearingTicksMax", theEntity.getRearingTicksMax());
        compound.setBoolean("isRearing", theEntity.isRearing());
    }

@Override
    public void loadNBTData(NBTTagCompound parCompound)
{
        // DEBUG
        System.out.println("ExtendedPropertiesHerdAnimal loadNBTData()");

        // Get the sub-compound
        NBTTagCompound compound = (NBTTagCompound) 
        parCompound.getTag(extendedPropertiesName);

        theEntity.setScaleFactor(compound.getFloat("scaleFactor"));
        theEntity.setRearingCounter(compound.getInteger("rearingCounter"));
        theEntity.setRearingTicksMax(compound.getInteger("rearingTicksMax"));
        theEntity.setRearing(compound.getBoolean("isRearing"));
   }

   @Override
   public void init(Entity entity, World world)
   {
        // DEBUG
        System.out.println("ExtendedPropertiesHerdAnimal init()");
        theEntity = (EntityElephant)entity;
        theWorld = world;
    }
}

Step 2. Register Entity Extended Properties


To ensure that the extended properties are activated, you need to register them.  In your mod’s custom event handler class (the one that is subscribed to the EVENT_BUS) subscribe to the onEntityConstructing() event.

Inside the method subscribed to the event, for each entity that you want to have extended properties check that the entity is of the type you want to register, then register the associated extended properties class.

Example:

@SubscribeEvent
public void onEntityConstructing(EntityConstructing event)
{
    // Register extended entity properties
    // Herd animals

    if (event.entity instanceof EntityHorse)
    {
        // DEBUG
        System.out.println("OnEntityConstructing register EntityHorse
              extended properties");

         event.entity.registerExtendedProperties("ExtendedPropertiesHerdAnimal", 
               new ExtendedPropertiesHerdAnimal());
         }
    }
}

 

Conclusion


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

1 comment:

  1. Thanks for this information.

    It looks like your link to Forge's example Capability implementation is broken.

    ReplyDelete