Jump to content

[1.15.2] How to add a capability to the player entity


PenWallet

Recommended Posts

First of all, I'm quite new to modding, so I'm sorry if I'm doing many beginners' errors.

 

Hello, I've been reading some stuff about capabilities, but most stuff I find is from 1.12 and it appears to be outdated. I think.

I want to implement sort of a currency, so that each player would have its own amount registered to it. What I have so far is this:

 

ICurrency.java

public interface ICurrency {

    public int getAmount();
    public void setAmount(int amount);
    public void addOrSubtractAmount(int amount);
}

 

Currency.java

public class Currency implements ICurrency {
    private int amount = 0;

    @Override
    public int getAmount() {
        return this.amount;
    }

    @Override
    public void setAmount(int amount) {
        this.amount = amount;

        if(this.amount < 0)
            this.amount = 0;
    }

    @Override
    public void addOrSubtractAmount(int amount) {
        this.amount += amount;

        if(this.amount < 0)
            this.amount = 0;
    }
}

 

CurrencyStorage.java

public class CurrencyStorage implements Capability.IStorage<ICurrency> {
    @Nullable
    @Override
    public INBT writeNBT(Capability<ICurrency> capability, ICurrency instance, Direction side) {
        return IntNBT.func_229692_a_(instance.getAmount());
    }

    @Override
    public void readNBT(Capability<ICurrency> capability, ICurrency instance, Direction side, INBT nbt) {
        if (!(instance instanceof Currency))
            throw new IllegalArgumentException("Can not deserialize to an instance that isn't the default implementation");

        instance.setAmount(((IntNBT)nbt).getInt());
    }
}

 

CurrencyCapability.java

public class CurrencyCapability implements ICapabilitySerializable<IntNBT> {

    @CapabilityInject(ICurrency.class)
    public static final Capability<ICurrency> CURRENCY_CAPABILITY = null;
    private LazyOptional<ICurrency> instance = LazyOptional.of(CURRENCY_CAPABILITY::getDefaultInstance);

    public static void register()
    {
        CapabilityManager.INSTANCE.register(ICurrency.class, new CurrencyStorage(), Currency::new);
    }

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        return CURRENCY_CAPABILITY.orEmpty(cap, instance);;
    }

    @Override
    public IntNBT serializeNBT() {
        return (IntNBT) CURRENCY_CAPABILITY.getStorage().writeNBT(CURRENCY_CAPABILITY, instance.orElseThrow(() -> new IllegalArgumentException("LazyOptional cannot be empty!")), null);;
    }

    @Override
    public void deserializeNBT(IntNBT nbt) {
        CURRENCY_CAPABILITY.getStorage().readNBT(CURRENCY_CAPABILITY, instance.orElseThrow(() -> new IllegalArgumentException("LazyOptional cannot be empty!")), null, nbt);
    }
}

 

Where I'm registering the capability

@Mod(Constants.MODID)
public class Mod {
  
  public Mod() {
    FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
    //More events and stuff...
  }
  
  @SubscribeEvent
  public void attachCapabilitiesEntity(final AttachCapabilitiesEvent<Entity> event)
  {
      if(event.getObject() instanceof PlayerEntity)
      	event.addCapability(new ResourceLocation(Constants.MODID, Constants.CURRENCY), new CurrencyCapability());
  }
  
  private void setup(final FMLCommonSetupEvent event) {
        //Register the Currency capability
        CurrencyCapability.register();
   }
  
  //More events and stuff...
}

 

My problems lie on various places:

1. CurrencyCapability throws errors: .orEmpty, and CURRENCY_CAPABILITY.getStorage() say "Method invocation 'orEmpty' will produce 'NullPointerException' " and can't use it

2. I'm not sure if I'm registering it correctly

3. I don't know, once I'm able to register it correctly, how to get the info (the currency amount, that it) from the player

 

Thank you for your time

Link to comment
Share on other sites

2 hours ago, diesieben07 said:

PlayerEntity (and all entities in fact) implement ICapabilityProvider, so you just call getCapability.

Right now I'm just testing so that if I right click on the item, it shows the current amount, then prints out a message to the user. It works, but it looks like an awful lot of code to get the information. Would this be the way to access its information? Is there a better way?

public class CopperIngot extends Item {

    public CopperIngot() {
        super(new Item.Properties().group(Constants.SMITEMGROUP));
        setRegistryName(Constants.MODID, Constants.COPPERINGOT);
    }

    @Override
    public ActionResult<ItemStack> onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn) {
        if(!worldIn.isRemote)
            playerIn.getCapability(CurrencyCapability.CURRENCY_CAPABILITY).ifPresent(new NonNullConsumer<ICurrency>() {
                @Override
                public void accept(@Nonnull ICurrency iCurrency) {
                    playerIn.sendStatusMessage(new StringTextComponent("Currency: "+iCurrency.getAmount()), false);
                    iCurrency.addOrSubtractAmount(1);
                }
            });
        return super.onItemRightClick(worldIn, playerIn, handIn);
    }
}

 

Link to comment
Share on other sites

24 minutes ago, diesieben07 said:

You might also want to consider using orElseThrow instead of ifPresent, since you add the capability to all players it would be an error for it to not be there.

Damn, that worked like a charm, and it also reduced the amount of lines, thanks a lot.

 

One last thing. If the mod were to be used in multiplayer, would I have synchronization problems? I read that on https://mcforge.readthedocs.io/en/1.15.x/datastorage/capabilities/#synchronizing-data-with-clients, but I'm unsure if that still applies, or if it won't be a problem at all

Link to comment
Share on other sites

Just now, diesieben07 said:

If you need the data on the client then you need to do the synchronization yourself, that still applies.

If you only need the data on the server the you don't need to do anything else.

So, if I wanted to (which is what I want to do) show the current amount on the inventory. That's the client, so I would have to do synchronization, right?

Link to comment
Share on other sites

2 minutes ago, diesieben07 said:

Correct, you'd have to send the data to the player it is attached to.

Alright, thank you very much for your help. I'll try later on the day to do the showing on the inventory, but for now that's all I needed, thanks a lot again.

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.