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.

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());


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.

No comments:

Post a Comment