javaminecraftminecraft-forge

Capabilities stopped saving forge 1.12.2


I used this tutorial to add capabilities to my mod although when I changed them to suit my mod it stopped saving whenever I left the world. I'm pretty sure that the problem has something to do with writing the data to the player because when I change the code inside of the readNBT function to just run with a number inside of the set function instead of reading from nbt, it still doesn't change anything. I know the function is still being run though because if I place System.out.println in it, it'll still output something.
Anyways heres my code inside my capabilities-related files:

public class StatusStorage implements Capability.IStorage<IStatus>
{
    @Override
    public NBTBase writeNBT(Capability<IStatus> capability, IStatus instance, EnumFacing side)
    {
        NBTTagCompound status = new NBTTagCompound();
        status.setInteger("hasFalna", instance.get(0));
        status.setInteger("strength", instance.get(1));
        status.setInteger("edurance", instance.get(2));
        status.setInteger("dexterity", instance.get(3));
        status.setInteger("agility", instance.get(4));
        status.setInteger("magic", instance.get(5));
        status.setInteger("level", instance.get(6));
        status.setString("familia", instance.getFamilia());
        return status;
    }

    @Override
    public void readNBT(Capability<IStatus> capability, IStatus instance, EnumFacing side, NBTBase nbt)
    {
        if(nbt instanceof NBTTagCompound)
        {
            NBTTagCompound tag = (NBTTagCompound)nbt;

            instance.set(tag.getInteger("hasFalna"), 0);
            instance.set(tag.getInteger("strength"), 1);
            instance.set(tag.getInteger("endurance"), 2);
            instance.set(tag.getInteger("dexterity"), 3);
            instance.set(tag.getInteger("agility"), 4);
            instance.set(tag.getInteger("magic"), 5);
            instance.set(tag.getInteger("level"), 6);
            instance.setFamilia(tag.getString("familia"));

            System.out.println("code ran");
        }
    }
}
public class StatusProvider implements ICapabilitySerializable<NBTBase>
{
    @CapabilityInject(IStatus.class)
    public static final Capability<IStatus> STATUS_CAP = null;

    private IStatus instance = STATUS_CAP.getDefaultInstance();

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

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

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

    @Override
    public void deserializeNBT(NBTBase nbt)
    {
        STATUS_CAP.getStorage().readNBT(STATUS_CAP, this.instance, null, nbt);
    }
}
public class Status implements IStatus
{
    private int hasFalna = 0;
    private int strength = 0;
    private int endurance = 0;
    private int dexterity = 0;
    private int agility = 0;
    private int magic = 0;
    private int level = 1;

    private String familia = "";

    @Override
    public void decrease(int points)
    {
        this.strength -= points;

        if (this.strength < 0.0F) this.strength = 0;
    }

    @Override
    public void increase(int points)
    {
        this.strength += points;
    }

    @Override
    public void set(int stat, int id)
    {
        switch(id)
        {
            case 0:
                this.hasFalna = stat;
                break;

            case 1:
                this.strength = stat;
                break;

            case 2:
                this.endurance = stat;
                break;

            case 3:
                this.dexterity = stat;
                break;

            case 4:
                this.agility = stat;
                break;

            case 5:
                this.magic = stat;
                break;

            case 6:
                this.level = stat;
                break;
        }
    }

    @Override
    public int get(int id)
    {
        switch(id)
        {
            case 0:
                return this.hasFalna;

            case 1:
                return this.strength;

            case 2:
                return this.endurance;

            case 3:
                return this.dexterity;

            case 4:
                return this.agility;

            case 5:
                return this.magic;

            case 6:
                return this.level;
        }

        return 0;
    }

    @Override
    public void giveFalna()
    {
        hasFalna = 1;
    }

    @Override
    public boolean getFalna()
    {
        return (hasFalna == 1);
    }

    @Override
    public String getFamilia()
    {
        return familia;
    }

    @Override
    public void setFamilia(String familia)
    {
        this.familia = familia;
    }
}
public interface IStatus
{
    public void giveFalna();
    public boolean getFalna();

    public String getFamilia();
    public void setFamilia(String familiaName);

    public void increase(int points);
    public void decrease(int points);
    public void set(int falna, int id);

    public int get(int id);
}
public class CapabilityHandler
{
    public static final ResourceLocation STATUS_CAP = new ResourceLocation(Reference.MODID, "status");

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

        event.addCapability(STATUS_CAP, new StatusProvider());
    }
}
public class EventHandler
{
    @SubscribeEvent
    public void onPlayerAttack(AttackEntityEvent event)
    {
        Entity entity = event.getEntity();

        if(!(entity instanceof EntityPlayer)) return;

        EntityPlayer player = (EntityPlayer)entity;
        IStatus status = player.getCapability(StatusProvider.STATUS_CAP, null);

        if(!status.getFalna()) return;

        status.increase(1);
    }
}
public class CommonProxy
{
    public void init()
    {
        CapabilityManager.INSTANCE.register(IStatus.class, new StatusStorage(), Status::new);

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

Solution

  • I asked this question a while ago so I don't remember it to well, but if anyone stumbles across this the issue was that I didn't have any packets, which is what syncs the capabilities on the client and the server. A good guide for it is https://forge.gemwire.uk/wiki/Networking, and in my case what I did was create a message base:

    public abstract class MessageBase<REQ extends IMessage> implements IMessage, IMessageHandler<REQ, REQ> { 
    
        @Override
        public REQ onMessage(REQ message, MessageContext ctx) {
            if (ctx.side == Side.SERVER)
            {
                handleServerSide(message, ctx.getServerHandler().player);
            } 
            else
            {
                EntityPlayer player = ClientThings.getPlayer();
    
                if (player != null)
                {
                    handleClientSide(message, player);
                }
            }
    
            return null;
        }
    
        public abstract void handleClientSide(REQ message, EntityPlayer player);
    
        public abstract void handleServerSide(REQ message, EntityPlayer player);
    }
    

    And this will just be extended by another class that'll be your message.

    Some explanation for this code is the onMessage function is ran, well, whenever the message is sent, and the code inside of it just checks if it's being sent on the client or the server. The ClientThings.getPlayer(); just does Minecraft.getMinecraft().player but in a separate class that's only ever ran on the client, this way your mod wont crash on servers as Minecraft.getMinecraft() is a client only method and will return a method not found exception on any server side code when ran server-only.

    After you create the MessageBase, you create your actual message, which will look something like this:

    public class MessageCap extends MessageBase<MessageCap>
    {
        private IExampleCapability capability = new Capability();
        private EntityPlayer player;
    
        @Override
        public void handleClientSide(MessageCap message, EntityPlayer player)
        {
            IExampleCapability capability = message.cap;
    
            if (player != null)
            {
                IExampleCapability old = player.getCapability(CapabilityProvider.CAPABILITY_CAP, Capability.capSide);
    
                old.setSomeInt(capability.getSomeInt(0));
            }
        }
    
        @Override
        public void handleServerSide(MessageCap message, EntityPlayer player)
        {
            EntityPlayerMP playerMP = (EntityPlayerMP)player;
            IExampleCapability capibility = message.cap;
            IExampleCapability old = playerMP.getCapability(CapabilityProvider.CAPABILITY_CAP, Capability.capSide);
    
            old.setSomeInt(capability.getSomeInt());
        }
    
        @Override
        public void fromBytes(ByteBuf buf)
        {
            if(buf.isReadable())
            {
                cap.setSomeInt = buf.readInt();
            }
        }
    
        public MessageCap(IExampleCapability cap, EntityPlayer player)
        {
            this.cap = cap;
            this.player = player;
        }
    
        public MessageCap()
        {
        }
    }
    

    Inside of the fromBytes and toBytes functions, you'll want to convert the capability and bytes with one another, and in the handleServerSide and handleClientSide you'll just update your current capability with the capability the function gives you.

    Then finally you'll need a capability handler so you can send packets,

    public class NetworkHandler
    {
        private static SimpleNetworkWrapper INSTANCE;
        private static SimpleNetworkWrapper INSTANCE_2;
    
        public static void init()
        {
            INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel(Reference.MODID);
            INSTANCE.registerMessage(MessageCap.class, MessageCap.class, 0, Side.SERVER);
    
            INSTANCE_2 = NetworkRegistry.INSTANCE.newSimpleChannel(Reference.MODID + "_client");
            INSTANCE_2.registerMessage(MessageCap.class, MessageCap.class, 0, Side.CLIENT);
        }
    
        public static void sendToServer(IMessage message)
        {
            INSTANCE.sendToServer(message);
        }
    
        public static void refreshThing(IMessage message, EntityPlayerMP player)
        {
            INSTANCE_2.sendTo(message, player);
        }
    }
    

    And this isn't a very good implementation, as I know you're supposed to only need one instance of a network wrapper, but mine only works when there is one for both the client and the server.

    Finally now that you have all of this, whenever you change the capability on the server you'll want to call

    NetworkHandler.refreshThing(new MessageCap(capability, player), player);
    

    to refresh it on the client, and when you change something on the client you'll want to call

    NetworkHandler.sendToServer(new MessageStatus(capability, player));