Posted October 4, 20169 yr I want to edit the vanilla player inventory - essentially, change the number of ItemStacks the player can carry. My plan to achieve this is to use a Capability to store my custom inventory in the player entity, then use events to make sure that items only go into my custom inventory (not the vanilla one), as well as to make sure the right inventory is used for the GUI. I've found lots of information about Capabilities but it all seems to assume a level of knowledge I just don't have. I've read through the code in detail but I'm just really confused about the various different but similar classes and interfaces. I've never used the old IExtendedEntityProperties system so I don't have anything to learn from. Can anyone give me a really basic from-scratch guide to adding a Capability to EntityPlayer to store an inventory of ItemStacks? Exactly what classes do I need to make, and what methods do I need to call where? Or alternatively, tell me if (and why) my plan to use a Capability for this won't work - and advise on a different/better way to do it?
October 5, 20169 yr Here is the documentation for Capabilities , this will help you get started: http://mcforge.readthedocs.io/en/latest/datastorage/capabilities/ Don't PM me with questions. They will be ignored! Make a thread on the appropriate board for support. 1.12 -> 1.13 primer by williewillus. 1.7.10 and older versions of Minecraft are no longer supported due to it's age! Update to the latest version for support. http://www.howoldisminecraft1710.today/
October 5, 20169 yr Author I've read that, a lot. Most of the tutorials and information I can find seems to be about creating custom Capabilities, and brushes off the inbuilt ones as easy - but I can't figure it out! I'm pretty sure I need to use the addCapability method from AttachCapabilityEvent.Entity . So I need something like: event.addCapability( ResourceLocation , ICapabilityProvider ) ; But I don't know what those arguments should be. The first one is just like a name, right? So something like new ResourceLocation( Main.MODID , "invcap" ) I'm guessing. But I have no idea what ICapabilityProvider argument I need. I read that Entities themselves are ICapabilityProvider s. Does that mean I can just pass the player from the event as the second argument, like this? event.addCapability( new ResourceLocation( Main.MODID , "invcap" ) , event.getEntity() ) ; But this doesn't allow anywhere to actually define the Capability , like to specify that I want it to be an IItemHandler or say how many slots it should contain. What am I missing?
October 5, 20169 yr Forge already patches EntityLivingBase and various subclasses to provide CapabilityItemHandler.ITEM_HANDLER_CAPABILITY . This takes priority over custom providers attached with AttachCapabilitiesEvent , so attaching providers for CapabilityItemHandler.ITEM_HANDLER_CAPABILITY to living entities won't work. What you need to do is create your own interface that extends IItemHandler (it doesn't need to define any new methods) and register a capability for it. You can then use AttachCapabilitiesEvent to attach providers for this capability to players. The ICapabilityProvider you attach to an external object with AttachCapabilitiesEvent should be a new instance of your own class, not the ICapabilityProvider that the event was fired for. It also needs to implement INBTSerializable to load the capability from and save the capability to NBT. Forge provides the ICapabilitySerializable interface for convenience, this is simply a combination of ICapabilityProvider and INBTSerializable . For examples of capabilities, you can look at the capabilities provided by Forge (look for usages of CapabilityManager#register ), the capability test mod or my own mod's capabilities (API, implementation). Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.
October 5, 20169 yr Author Thanks for the advice. I've made a start, maybe you could tell me whether or not I'm on the right track so far? None of my classes/methods have any actual content yet, first I just want to make sure I'm getting the structure of the interfaces/inheritance/etc right. My capability interface: package com.jj.jjmod.inventory.player; import net.minecraftforge.items.IItemHandler ; public interface ICustomInventoryCap extends IItemHandler { } My IStorage : package com.jj.jjmod.inventory.player; import net.minecraft.nbt.NBTBase ; import net.minecraft.util.EnumFacing ; import net.minecraftforge.common.capabilities.Capability ; import net.minecraftforge.common.capabilities.Capability.IStorage ; public class InventoryStorage implements IStorage<ICustomInventoryCap> { @Override public NBTBase writeNBT( Capability< ICustomInventoryCap > capability , ICustomInventoryCap instance , EnumFacing side ) { // TODO Auto-generated method stub return null ; } @Override public void readNBT( Capability< ICustomInventoryCap > capability , ICustomInventoryCap instance , EnumFacing side , NBTBase nbt ) { // TODO Auto-generated method stub } } My ICapabilitySerializable : package com.jj.jjmod.inventory.player; import net.minecraft.nbt.NBTBase ; import net.minecraft.util.EnumFacing ; import net.minecraftforge.common.capabilities.Capability ; import net.minecraftforge.common.capabilities.ICapabilitySerializable ; public class InventoryProvider implements ICapabilitySerializable { @Override public boolean hasCapability( Capability< ? > capability , EnumFacing facing ) { if ( capability == CustomInventoryDef.CUSTOM_INVENTORY ) { return true ; } return false ; } @Override public < T > T getCapability( Capability< T > capability , EnumFacing facing ) { if ( capability == CustomInventoryDef.CUSTOM_INVENTORY ) { return (T) CustomInventoryDef.CUSTOM_INVENTORY ; } return null ; } @Override public NBTBase serializeNBT() { // TODO Auto-generated method stub return null ; } @Override public void deserializeNBT( NBTBase nbt ) { // TODO Auto-generated method stub } } And my default implementation: package com.jj.jjmod.inventory.player ; import com.jj.jjmod.main.Main ; import net.minecraft.item.ItemStack ; import net.minecraft.util.ResourceLocation ; import net.minecraftforge.common.capabilities.Capability ; import net.minecraftforge.common.capabilities.CapabilityInject ; public class CustomInventoryDef implements ICustomInventoryCap { @CapabilityInject( ICustomInventoryCap.class ) public static final Capability< ICustomInventoryCap > CUSTOM_INVENTORY = null ; public static final ResourceLocation ID = new ResourceLocation( Main.MODID , "CustomInventory" ) ; @Override public int getSlots() { // TODO Auto-generated method stub return 9 ; } @Override public ItemStack getStackInSlot( int slot ) { // TODO Auto-generated method stub return null ; } @Override public ItemStack insertItem( int slot , ItemStack stack , boolean simulate ) { // TODO Auto-generated method stub return null ; } @Override public ItemStack extractItem( int slot , int amount , boolean simulate ) { // TODO Auto-generated method stub return null ; } } I register it with this line in CommonProxy : CapabilityManager.INSTANCE.register( ICustomInventoryCap.class , new InventoryStorage() , CustomInventoryDef.class ) ; And attach it with this event in my EventHandler : @SubscribeEvent public void attachEntityCapabilities( AttachCapabilitiesEvent.Entity event ) { if ( !( event.getEntity() instanceof EntityPlayer ) ) { return ; } event.addCapability( CustomInventoryDef.ID , new InventoryProvider() ) ; } I've tested it and it runs with no crashes, so I'm tentatively positive..?
October 5, 20169 yr My capability interface: package com.jj.jjmod.inventory.player; import net.minecraftforge.items.IItemHandler ; public interface ICustomInventoryCap extends IItemHandler { } There's already an IItemHandler cap. And you don't need to extend it at all. net.minecraftforge.items.ItemStackHandler https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/entities/TileEntitySifter.java#L33 Mind, I did, in order to have "slots" that could only take certain items: https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/item/SiftableItemsHandler.java But it's definitely not necessary. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
October 5, 20169 yr Author My capability interface: package com.jj.jjmod.inventory.player; import net.minecraftforge.items.IItemHandler ; public interface ICustomInventoryCap extends IItemHandler { } There's already an IItemHandler cap. And you don't need to extend it at all. net.minecraftforge.items.ItemStackHandler https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/entities/TileEntitySifter.java#L33 Mind, I did, in order to have "slots" that could only take certain items: https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/item/SiftableItemsHandler.java But it's definitely not necessary. Ohh, I misinterpreted when Choonster said you can't attach CapabilityItemHandler.ITEM_HANDLER_CAPABILITY , and thought that meant you couldn't attach anything that directly implemented IItemHandler . In any case, I plan to have custom slots and various other things going on, so it's probably best to make my own.
October 5, 20169 yr Then my Sifter class will be perfect for an example. I has a custom input slot, a generic output slot, a GUI (the Caps method made that confusing), and so on. Take a look at .getCapability(...) too. And trust me, figuring all those things out wasn't easy and there aren't any tutorials (that are any good) yet. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
October 5, 20169 yr Author I'm confused again. I tried to use getCapability on the Player and I'm getting a crash with this error: java.lang.ClassCastException: net.minecraftforge.common.capabilities.Capability cannot be cast to com.jj.jjmod.inventory.player.ICustomInventoryCap . I don't get why it's crashing because getCapability is supposed to be returning a ICustomInventoryCap so there should be no casting involved. What am I missing?
October 5, 20169 yr Of course there's a cast involved. The method is a generic. It takes in a Capability<T> and returns T. So in the code there's a return (T) someCap line that sends the capability back to you. If the requested capability<T> doesn't return an object of Type T, then the cast fails. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
October 5, 20169 yr Author Of course there's a cast involved. The method is a generic. It takes in a Capability<T> and returns T. So in the code there's a return (T) someCap line that sends the capability back to you. If the requested capability<T> doesn't return an object of Type T, then the cast fails. Ohh yes, I see. But I still don't understand why the cast isn't working. How do I make sure my Capability can be cast to my Interface? It already implements it (it's the same code from my post above).
October 5, 20169 yr Is this still what your code does? @Override public < T > T getCapability( Capability< T > capability , EnumFacing facing ) { if ( capability == CustomInventoryDef.CUSTOM_INVENTORY ) { return (T) CustomInventoryDef.CUSTOM_INVENTORY ; } return null ; } If so, you're not returning T there, you're returning Capability<T>. Cap<T> is like an Enum, it's like saying "PINK" when what you need to return is Blocks.wool (the thing that is pink). Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
October 5, 20169 yr Author Yeah, that's the code that's causing the crash. This is making my head hurt. Do I need to return the Player that the capability belongs to? (Thanks everyone for helping, I know I'm being a noob, but I really want to learn)
October 5, 20169 yr Take a look here: https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/entities/TileEntitySifter.java#L124-L126 ITEM_HANDLER_CAPABILITY isn't ItemStackHandler Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
October 6, 20169 yr Thanks for the advice. I've made a start, maybe you could tell me whether or not I'm on the right track so far? None of my classes/methods have any actual content yet, first I just want to make sure I'm getting the structure of the interfaces/inheritance/etc right. Your ICapabilityProvider class needs to store the instance of ICustomInventoryCap that it provides from getCapability . I recommend separating the Capability field and ID from the default implementation of ICustomInventoryCap ( CustomInventoryDef ). In my code I create a dedicated class for each capability to handle registration and store the Capability instance (though there's no requirement to store it in a single place, @CapabiltiyInject will inject the instance anywhere). In my new mod, I store the Capability in an API class instead of the registration class. There's already an IItemHandler cap. And you don't need to extend it at all. net.minecraftforge.items.ItemStackHandler https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/entities/TileEntitySifter.java#L33 Mind, I did, in order to have "slots" that could only take certain items: https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/ores/item/SiftableItemsHandler.java But it's definitely not necessary. The OP needs their own capability so they can store an IItemHandler inventory with an EntityPlayer . EntityLivingBase already provides CapabilityItemHandler.ITEM_HANDLER_CAPABILITY , so attaching a provider for it with AttachCapabilitiesEvent won't work. Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.
October 6, 20169 yr The OP needs their own capability so they can store an IItemHandler inventory with an EntityPlayer . EntityLivingBase already provides CapabilityItemHandler.ITEM_HANDLER_CAPABILITY , so attaching a provider for it with AttachCapabilitiesEvent won't work. Yes, but that was provided as a reference to show the difference between the Cap<T> and the T. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
October 6, 20169 yr Author Your ICapabilityProvider class needs to store the instance of ICustomInventoryCap that it provides from getCapability . Okay, I've done some rearranging. For testing purposes, I'm trying to get the Capability to store a string that it gets from the name of the last item picked up by the player. I made a new class to store the Capability instance and ID, as you recommended: package com.jj.jjmod.inventory.player; import com.jj.jjmod.main.Main ; import net.minecraft.util.ResourceLocation ; import net.minecraftforge.common.capabilities.Capability ; import net.minecraftforge.common.capabilities.CapabilityInject ; public class CustomInventoryCap { @CapabilityInject( ICustomInventoryCap.class ) public static final Capability< ICustomInventoryCap > CUSTOM_INVENTORY = null ; public static final ResourceLocation ID = new ResourceLocation( Main.MODID , "CustomInventory" ) ; } My ICUstomInventoryCap , I've just added a method to get the stored string. package com.jj.jjmod.inventory.player; import net.minecraftforge.items.IItemHandler ; public interface ICustomInventoryCap extends IItemHandler { public String getItem() ; } The default implementation, again the only change is simple methods to save and retrieve the test string: package com.jj.jjmod.inventory.player ; import com.jj.jjmod.main.Main ; import net.minecraft.item.ItemStack ; import net.minecraft.util.ResourceLocation ; import net.minecraftforge.common.capabilities.Capability ; import net.minecraftforge.common.capabilities.CapabilityInject ; public class CustomInventoryDef implements ICustomInventoryCap { public String item ; @Override public int getSlots() { // TODO Auto-generated method stub return 9 ; } @Override public ItemStack getStackInSlot( int slot ) { // TODO Auto-generated method stub return null ; } @Override public ItemStack insertItem( int slot , ItemStack stack , boolean simulate ) { item = stack.toString() ; return null ; } @Override public String getItem() { return item ; } @Override public ItemStack extractItem( int slot , int amount , boolean simulate ) { // TODO Auto-generated method stub return null ; } } The InventoryProvider now stores an instance of ICustomInventoryCap which it takes as an argument in its constructor, and returns that for getCapability : package com.jj.jjmod.inventory.player; import net.minecraft.inventory.IInventory ; import net.minecraft.nbt.NBTBase ; import net.minecraft.util.EnumFacing ; import net.minecraftforge.common.capabilities.Capability ; import net.minecraftforge.common.capabilities.ICapabilitySerializable ; import net.minecraftforge.items.wrapper.InvWrapper ; public class InventoryProvider implements ICapabilitySerializable { ICustomInventoryCap inv ; public InventoryProvider( ICustomInventoryCap inv ) { this.inv = inv ; } @Override public boolean hasCapability( Capability< ? > capability , EnumFacing facing ) { if ( capability == CustomInventoryCap.CUSTOM_INVENTORY ) { return true ; } return false ; } @Override public < T > T getCapability( Capability< T > capability , EnumFacing facing ) { System.out.println( "getting capability" ) ; if ( capability == CustomInventoryCap.CUSTOM_INVENTORY ) { System.out.println( "capability is custom inventory" ) ; return (T) this.inv ; } return null ; } @Override public NBTBase serializeNBT() { // TODO Auto-generated method stub return null ; } @Override public void deserializeNBT( NBTBase nbt ) { // TODO Auto-generated method stub } } In my EventHandler , the addCapability now supplies a new instance of the default implementation as the argument to the InventoryProvider constructor. Plus a call to the Capability's insertItem in ItemPickupEvent to save the string. @SubscribeEvent public void attachEntityCapabilities( AttachCapabilitiesEvent.Entity event ) { if ( !( event.getEntity() instanceof EntityPlayer ) ) { return ; } event.addCapability( CustomInventoryCap.ID , new InventoryProvider(new CustomInventoryDef()) ) ; } @SubscribeEvent public void itemPickup( ItemPickupEvent event ) { // TEST capability System.out.println( "inserting " + event.pickedUp.getEntityItem() ) ; event.player.getCapability( CustomInventoryCap.CUSTOM_INVENTORY , null ).insertItem( 0 , event.pickedUp.getEntityItem() , true ) ; } It's still not crashing, which is good news! And when I call the getItem method from another arbitrary event, it seems to be correctly returning the last item picked up. Have I made any more glaring mistakes yet? Next step, figuring out the IStorage ...
October 6, 20169 yr That looks correct apart from the unimplemented methods. I suggest extending ItemStackHandler for your default implementation so you don't have to re-implement all the IItemHandler logic yourself. You can still override methods to add custom behaviour as needed. I also suggest making the InventoryProvider#inv field private (so it can't be accessed by external classes except through the ICapabilityProvider API) and final (so it can't be replaced). Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.
October 6, 20169 yr Author That looks correct apart from the unimplemented methods. I suggest extending ItemStackHandler for your default implementation so you don't have to re-implement all the IItemHandler logic yourself. You can still override methods to add custom behaviour as needed. I also suggest making the InventoryProvider#inv field private (so it can't be accessed by external classes except through the ICapabilityProvider API) and final (so it can't be replaced). Thank you! Now I'm trying to work out what to do about IStorage and NBT. There are NBT-related methods in a bunch of different classes - in my default implementation (via ItemStackHandler ), in InventoryProvider , and in InventoryStorage . Do some of those need to cross-reference each other, and if so how? Also, do I need to actually manually call any of those methods, like in my EventHandler , or will they be called by Forge automatically anyway?
October 7, 20169 yr That looks correct apart from the unimplemented methods. I suggest extending ItemStackHandler for your default implementation so you don't have to re-implement all the IItemHandler logic yourself. You can still override methods to add custom behaviour as needed. I also suggest making the InventoryProvider#inv field private (so it can't be accessed by external classes except through the ICapabilityProvider API) and final (so it can't be replaced). Thank you! Now I'm trying to work out what to do about IStorage and NBT. There are NBT-related methods in a bunch of different classes - in my default implementation (via ItemStackHandler ), in InventoryProvider , and in InventoryStorage . Do some of those need to cross-reference each other, and if so how? Also, do I need to actually manually call any of those methods, like in my EventHandler , or will they be called by Forge automatically anyway? The IStorage implementation should have its own NBT reading/writing logic, like CapabilityItemHandler 's IStorage does. The default implementation of your capability should also have its own NBT reading/writing logic if it implements INBTSerializable . The ICapabilitySerializable should either use the IStorage or the instance's INBTSerializable methods to read/write NBT. Forge will automatically call the INBTSerializable methods of an ICapabilityProvider you attach with AttachCapabilitiesEvent or return from Item#initCapabilities . If you provide a capability from your own TileEntity / Entity , you're responsible for reading it from and writing it to NBT. Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.