Minecraft / Blogs

Forge tutorial: Capability System

Capabilities

  • 10
  • 7
  • comment32
  • playlist_add
  • share
  • more_horiz
avatar McHorse
Level 59 : Grandmaster Unicorn
123
Background: I was working on multiplayer support for my mod, and I needed to store some information in the player. Forge provided some mechanism called Capabilities (this system replaced mechanism that was previously known as IEEP). I spent few days figuring out how this mechanism works. The official documentation didn't really helped to understand how to use Capabilities, so I decided to write a tutorial of how to use this mechanism. I hope it will help you :)

This tutorial requires an understanding of basic concept of Forge modding (proxies).



From official documentation, Capabilities are:

... allow exposing features in a dynamic and flexible way, without having to resort to directly implementing many interfaces.

While it a valid explanation, but it's really wordy, filled with complicated terms, and not really clear. In my words:

Capabilities is a mechanism to store your custom interface implementations (functions and data) in Entities, TEs, ItemStacks and World with separate storage class that would be responsible for saving and loading data for your implementation. It also allows substituting default implementations in another mods.

Capabilities are actually pretty easy to use (if you'll find an code example in internet). I will only walk over about how to create your own capability.

Creating Capability

Let's say we want to store some information about player, like mana. Let's create capability for storing mana. So first we start out with creating and Java interface for your capability:

/**
* Mana capability
*/
public interface IMana
{
public void consume(float points);
public void fill(float points);
public void set(float points);

public float getMana();
}

Our mana capability would have methods for consuming, filling, setting and getting mana methods. That's our capability. Now that we have our interface, we need to create a default implementation for our capability. Our default implementation would be some basic class that implements IMana interface:

/**
* Default implementation of IMana
*/
public class Mana implements IMana
{
private float mana = 250.0F;

public void consume(float points)
{
this.mana -= points;

if (this.mana < 0.0F) this.mana = 0.0F;
}

public void fill(float points)
{
this.mana += points;
}

public void set(float points)
{
this.mana = points;
}

public float getMana()
{
return this.mana;
}
}

This is a simple implementation of our mana storage interface. Now that we have our capability interface and default implementation, we need to create a storage class that will be responsible for persisting our mana to world.

Creating Storage Class

Storage class (implements Capability.IStorage) is responsible for saving capability's data to NBT (as most of stuff in minecraft). For our mana capability, we'll have a pretty simple storage class:

/**
* This class is responsible for saving and reading mana data from or to server
*/
public class ManaStorage implements IStorage<IMana>
{
@Override
public NBTBase writeNBT(Capability<IMana> capability, IMana instance, EnumFacing side)
{
return new NBTTagFloat(instance.getMana());
}

@Override
public void readNBT(Capability<IMana> capability, IMana instance, EnumFacing side, NBTBase nbt)
{
instance.set(((NBTPrimitive) nbt).getFloat());
}
}

Nothing complicated, just an implementation of IStorage interface that responsible for reading data from and writing data to NBT. You can write or read any type of NBT class like starting from NBTPrimitive instances to NBTTagCompound. All you need to do is to stay consistent with your NBT read and return types.

Now that we have our capability and its storage, it's time to register our capability.

Registering our Capability

Registering a capability is actually a breeze. All you have to do is to place this one-liner to your common proxy:

CapabilityManager.INSTANCE.register(IMana.class, new ManaStorage(), Mana.class);
This line of code will make our capability available for injection via @CapabilityInject annotation. The final part of this Capabilities tutorial is attaching the capability to designated target (i.e. Entity, TE, ItemStack or World).

Attaching our Mana Capability

So, to make everything work, we need to attach our capability to the designated target (Entity, TE, ItemStack, or/and World). To do that, we must create an event handler that listens to AttachCabapilityEvent. In this tutorial, I'm going to attach my capability to player, however, this may be done to anything listed above.

Before attaching capability, we need another class that will provide capability:

/**
* Mana provider
*
* This class is responsible for providing a capability. Other modders may
* attach their own provider with implementation that returns another
* implementation of IMana to the target's (Entity, TE, ItemStack, etc.) disposal.
*/
public class ManaProvider implements ICapabilitySerializable<NBTBase>
{
@CapabilityInject(IMana.class)
public static final Capability<IMana> MANA_CAP = null;

private IMana instance = MANA_CAP.getDefaultInstance();

@Override
public boolean hasCapability(Capability<?> capability, EnumFacing facing)
{
return capability == MANA_CAP;
}

@Override
public <T> T getCapability(Capability<T> capability, EnumFacing facing)
{
return capability == MANA_CAP ? MANA_CAP.<T> cast(this.instance) : null;
}

@Override
public NBTBase serializeNBT()
{
return MANA_CAP.getStorage().writeNBT(MANA_CAP, this.instance, null);
}

@Override
public void deserializeNBT(NBTBase nbt)
{
MANA_CAP.getStorage().readNBT(MANA_CAP, this.instance, null, nbt);
}
}


So what this class does? This class is responsible for storing registered capability that will be injected via @CapabilityInject annotation (you can see how this implemented in CapabilityManager class), providing a capability based on the interface and saving/loading capability data. If you don't need to persist the data, you replace ICapabilitySerializable interface with ICapabilityProvider and remove serializeNBT and deserializeNBT methods).

The beauty of ICapabilityProvider (underlying interface of ICapabilitySerializable) is that other modders can substitute your capability provider and provide other implementation of your interface.

So now, finally, we can attach our capability to players:

/**
* Capability handler
*
* This class is responsible for attaching our capabilities
*/
public class CapabilityHandler
{
public static final ResourceLocation MANA_CAP = new ResourceLocation(ExampleMod.MODID, "mana");

@SubscribeEvent
public void attachCapability(AttachCapabilitiesEvent.Entity event)
{
if (!(event.getEntity() instanceof EntityPlayer)) return;

event.addCapability(MANA_CAP, new ManaProvider());
}
}

You need to register this event handler to MinecraftForge.EVENT_BUS in your common proxy.

The code is pretty simple here, we attach our capability to EntityPlayer (both on client and server). Now, next time you want to use your capability from the player, for example, consume or refill mana, you can use: Entity#getCapability(Capability<?> cap, EnumFacing face) method to retrieve IMana implementation. Let's make something that will consume mana or fill it.

Let's add another event handler that would be responsible for consuming and filling mana. This would be pretty simple, when player falls, it absorbs mana and shows in chat the amount of mana that was consumed and how much mana left. When players uses the bed, it will refill 50 mana points. Also when the player enters the game, it shows how much mana he has:

public class EventHandler
{
@SubscribeEvent
public void onPlayerLogsIn(PlayerLoggedInEvent event)
{
EntityPlayer player = event.player;
IMana mana = player.getCapability(ManaProvider.MANA_CAP, null);

String message = String.format("Hello there, you have §7%d§r mana left.", (int) mana.getMana());
player.addChatMessage(new TextComponentString(message));
}

@SubscribeEvent
public void onPlayerSleep(PlayerSleepInBedEvent event)
{
EntityPlayer player = event.getEntityPlayer();

if (player.worldObj.isRemote) return;

IMana mana = player.getCapability(ManaProvider.MANA_CAP, null);

mana.fill(50);

String message = String.format("You refreshed yourself in the bed. You received 50 mana, you have §7%d§r mana left.", (int) mana.getMana());
player.addChatMessage(new TextComponentString(message));
}

@SubscribeEvent
public void onPlayerFalls(LivingFallEvent event)
{
Entity entity = event.getEntity();

if (entity.worldObj.isRemote || !(entity instanceof EntityPlayerMP) || event.getDistance() < 3) return;

EntityPlayer player = (EntityPlayer) entity;
IMana mana = player.getCapability(ManaProvider.MANA_CAP, null);

float points = mana.getMana();
float cost = event.getDistance() * 2;

if (points > cost)
{
mana.consume(cost);

String message = String.format("You absorbed fall damage. It costed §7%d§r mana, you have §7%d§r mana left.", (int) cost, (int) mana.getMana());
player.addChatMessage(new TextComponentString(message));

event.setCanceled(true);
}
}
}


Attach this event handler in your common proxy, and you're good to go. Known bugs: you can spam the bed during the day light to gain a lot of mana points. It's an issue, but I created this event handler for demonstration how to use created capabilities.

If you want your values to be accessible on client side, you need to manually send packets to the client with values you want to change. See official documentation link for more information.

Recovering capability data on death

One problem (or rather flexible choice, in case if you don't want to keep the data) with capabilities, is that you need to copy data between dimensions and on death from died/teleported player to new player. It's done in a quite simple manner. We just need to add an event listener on player's clone (when player dies or gets teleported to another dimension), and simply copy the data from old player's capability to a new player's capability (added method to EventListener):

/**
* Copy data from dead player to the new player
*/
@SubscribeEvent
public void onPlayerClone(PlayerEvent.Clone event)
{
EntityPlayer player = event.getEntityPlayer();
IMana mana = player.getCapability(ManaProvider.MANA_CAP, null);
IMana oldMana = event.getOriginal().getCapability(ManaProvider.MANA_CAP, null);

mana.set(oldMana.getMana());
}

That's it! Now when a player gets killed or trasnferred to another dimension, the data from previous player will be recovered to a new player. For more complex capabilities, I recommend adding another method to capability interface named "copy" which will accept a capability as first argument from which it can copy the data. Take a look for example in Metamorph's source.

If you want to recover data only between dimensions (not on the death, though), take a look at PlayerEvent.Clone's method called isWasDeath(). It checks whether player died or was transferred to another dimension.

I also published a mod on github with working code of this tutorial. Feel free to check it out.



I hope you enjoyed the tutorial, and it was pretty clear and easy to understand. If you have any questions or problems, feel free to leave a comment.

Don't PM me with problems related to this tutorial, please, if you leave the comment with problem, and I provide a solution, it would benefit everyone, thanks! :)

For more information, see those resources:
P.S.: My definition of Forge's Capabilities mechanism is subjective, I put it as I understand Capabilities mechanism
P.S.S.: Sorry for formatting of the code, apparently PMC's formatter trims space indentation
Tags

1 Update Logs

Added a section about recovering data on death : 03/13/2017 4:09:06 amMar 13th, 2017

Just a short paragraph about how to transfer data from old player (which died or teleported to another dimension) to a new one.

Comments : 32

star Login or register to post a comment.

Show Comments

1 - 32 of 32

  • Meldexun
  • Level 1
  • New Miner
  • November 29, 2018, 9:07 am
Hi there, thank you for the tutorial. But once I leave my world and join again my mana is set to the default value. I'm modding in MC 1.12.2 and ingame I can increase, decrease or set or get my mana but its not saving.
  • Meldexun
  • Level 1
  • New Miner
  • November 29, 2018, 9:26 am
I found my issue. Your tutorial is fine :) Thank you
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • November 30, 2018, 10:17 pm
🙂👍
  • Cow333Moo
  • Level 1
  • New Miner
  • December 22, 2017, 9:32 am
Ok I got that working Thanks for your example but my mana bar won't work with it
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • December 22, 2017, 10:33 pm
What do you mean by "my mana bar won't work with it"?
  • Cow333Moo
  • Level 1
  • New Miner
  • December 21, 2017, 7:22 pm
I also am modding 1.10 as well and I don't know where to paste the Capability Manager in the server and client I know this is probably stupid but pleas help!
Thanks for the tutorial, it really helps, but I have a problem. I'm trying to follow this tutorial for minecraft 1.10.2, and it doesn't work. At first I thought it was because of the deprecated code, but I fixed that, and it still doesn't work. I was wondering if you could update this guide for newer versions, or at least specify the changes. If not I'll just move my mod to 1.9.4.
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • June 23, 2017, 1:54 am
The code for 1.9.4 is supposed to be exact same thing for 1.10.2. What errors do you get?
I don't get any errors, it just doesn't work. The game launches fine, but the capability's effects are nowhere to be seen. And I copied over everything identically. I'll try again and hope for better results I guess.
Okay, I figured it out. I was trying to put this into a mod I already had, and I didn't realize that I needed to put the common proxy code into the server and client proxies. I'm good now though. Thanks for the great tutorial, it'll really help me do what I want with my mod.
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • June 23, 2017, 11:59 am
Oh, I see the problem. I'm glad that you figured it out yourself, great job! :)
I'm also really glad that you found this tutorial very useful!
  • Cmonster
  • Level 1
  • New Miner
  • June 12, 2017, 4:11 pm
Currently i've created a fluid and am using forge universal buckets, but i don't know hoe to craft the the fluid with the bucket. Am I able to do this with capabilities? And if so, how? Thanks.
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • June 12, 2017, 10:57 pm
I haven't worked with fluid systems, and I don't think fluids are somehow related to capabilities. Sorry. You should try looking up source of mods that add a new fluid with craftable fluid buckets either via Java Decompiler or mod's source code repository (if that mod has one).
  • Cmonster
  • Level 1
  • New Miner
  • June 13, 2017, 1:00 pm
Thanks for the answer
Hey dude, nice job. Was very useful and was able to follow most of it on 1.12. I only had to change a few names and it was golden however. Im trying to use my mana pool in another class that uses the pool to calculate the mana bar. but it shows as a new instance. The values aren't updating when I decrease it in my item right click event. If you want or have time to take a look. here is the source: My Github page
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • April 24, 2017, 12:41 pm
Hey! I'm glad you found it helpful! :)

I guess you're talking about having issue with rendering the correct value of the mana on the client. The thing is, you need to sync the data from server's capability to the client using networking (I hope you familiar how to do networking in Forge). Basically what you need to do is to setup networking, create IMessage implementation which will be used to transfer value changes to the client, create client message handler which will be responsible for receiving and updating the client capability, add few event handlers for sending logged in/teleporting between dimension player capability's value and update the parts of the code where you update the mana on the server side to be able to send a message with changed value to the client.

I hope you understood what need is needed to be done. Otherwise, feel free to check out source code of my Metamorph's mod (it has code that updates capabilities when capability is changed). I could provide you with code, but I'm very busy on this week. I could help you with code next week.
Thank you so much for this tutorial. I am very new to networking and I am having a hard time sorting through the much more complicated capability system in Metamorphs. Is there any way you could add a gui system to your mana capability example? My system is essentially the same (1 field of float info) I usually understand the underlying concepts better when the example more closely matches what I am trying to do. Again, thank you so much for this tutorial it has helped me a lot.
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • June 12, 2017, 10:59 pm
Hey, sorry for a long delay in response, I haven't noticed this comment until now.
I don't really have time at the moment to update this tutorial for usage with GUI, but once I'll find some time, I'll update this tutorial, and let you know :)
So I'm not sure if this is a bug in dev mode and how things are handled through forge/minecraft users, but I have noticed that everytime I log into a world the data saved in my capabilities seems to span across the different player accounts. Is this just because it's dev mode or is this something I need to look into before I (eventually) release my mod?
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • March 12, 2017, 11:17 pm
Yes, it's a dev mode thing. Basically, Forge logs in as a new PlayerX (Where X number between 1-999) and creates that user a different UUID. It shouldn't happen in production. Don't worry about it :)
You should add a part where you show how to make the data save on death. I know forge documents says how to do it, but still I feel like it would add on to the tutorial. Just a suggestion!
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • March 12, 2017, 11:23 pm
Oh, dang! Sorry for a late reply. Yes, I'll add a section about how to recover capabilities on player's death! :)
  • owenn30
  • Level 1
  • New Miner
  • November 24, 2016, 4:38 pm
I'm on 1.10.2, the game still launched but i have 1 error and 2 deprecated functions. The error seems to be stopping mana from showing up for me at all. In common proxy i have:

MinecraftForge.EVENT_BUS.register(new EventHandler());

It is telling me that the "new EventHandler()" part is undefined and suggests i add an object, string, string, string argument.







In CapabilityHandler

@SubscribeEvent
public void attachCapability(AttachCapabilitiesEvent.Entity event) {
if (!(event.getEntity() instanceof EntityPlayer)) return;
event.addCapability(MANA_CAP, new ManaProvider());
}

Entity and getEntity are deprecated (i guess that's really only 1 deprecated function.)

Do you know what to use instead?
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • November 24, 2016, 10:29 pm
Hm, that's really weird. I used the same code in my other mods completely with no change. How did you resolved these classes? Show me your imports.

Check out the source repository:
https://github.com/mchorse/capabilities

Your code suppose to look alike.
  • owenn30
  • Level 1
  • New Miner
  • November 25, 2016, 5:29 am
Ugh my bad, import-all imported some javabeans.eventhandler nonsense. That's what i get for coding past midnight, immediately noticed it this morning. Thanks for posting this tutorial, i'm going to try to extend this to also keep track of chosen RPG class and exp gained.
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • November 25, 2016, 6:15 am
You're welcome! I like RPG stuff! Will you share your mod when it will come out? :)
  • owenn30
  • Level 1
  • New Miner
  • November 25, 2016, 6:35 am
Well i'm working on it alone, i haven't bothered telling any of my friends because i know it'll be a few months before it's done (minimum.) But i'll keep you in mind when i'm done making it. It's my first time modding minecraft so i'm still struggling with a lot of things.
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • November 25, 2016, 6:51 am
If you'll need any help, you can contact me via PMs, for example :)
  • Koenn
  • Level 14
  • Journeyman Crafter
  • November 20, 2016, 4:18 pm
Really helpful, you explained it much better as the documentation!
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • November 20, 2016, 10:22 pm
I'm glad it helped you :)
  • FEX___96
  • Level 74
  • Legendary Engineer
  • November 4, 2016, 12:51 pm
Somehow.. it worked...


thanks :)
  • McHorse
  • Level 59
  • Grandmaster Unicorn
  • November 4, 2016, 2:53 pm
Magic... :D
I'm glad you found it useful :)

1 - 32 of 32

Show Comments

Planet Minecraft

Browse

Site

© 2010 - 2019
planetminecraft.com

Welcome