Jump to content

Custom player inventory: Capability, ICapabilityProvider, PlayerInvWrapper...?


Jay Avery

Recommended Posts

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?

Link to comment
Share on other sites

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/

Link to comment
Share on other sites

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? :S

 

 

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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..?

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

I'm confused again. :D 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? :S

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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).

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

...

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.