Posted June 2, 20169 yr I've been trying to debug this for awhile now, and I'm not sure whether the issue is my understanding of capabilities or if I just need a second pair of eyes to help with my code. The code is intended to work as follows: 1) The client sends a packet containing the player name and an integer to the server. 2) The server gets the PlayerEntity from the name 3) The player's capability is called, and the integer is added to the array. The idea is to store which runes the player inputs from the keyboard, as certain rune combinations will make a spell. Unfortunately, the capability never gets more than one rune in the array, and the index is always at zero or one. My guess is that I am either always getting a new default implementation of the capability when calling the player's capability, or I need to do something extra to store capability data between calls. [spoiler=adding the capability] public class SpellEventHandler { @SubscribeEvent public void onEntityLoad(AttachCapabilitiesEvent.Entity event) { event.addCapability(new ResourceLocation(FantasyMod.MODID, "spellCasting"), new ProviderSpellCasting()); } } [spoiler=the capability interface] public interface ISpellCasting { public void addRune(EntityPlayer p, Runes r); public int[] getArray(); public void setArray(int[] set); } [spoiler=The default implementation] public class DefaultImplementation implements ISpellCasting { private Runes[] spell = new Runes[spell.MAX_RUNES_IN_SPELL]; private long[] castTimes = new long[spell.MAX_RUNES_IN_SPELL]; private int index; private static final float MAX_TIME_BETWEEN_CASTS = 3 * 1000; public DefaultImplementation() { for (int i = 0; i < Spell.MAX_RUNES_IN_SPELL; i++) { spell[i] = Runes.EMPTY; castTimes[i] = 0; } index = 0; } @Override public void addRune(EntityPlayer p, Runes r) { printArrays(); long curTime = System.currentTimeMillis(); // if (index < 0) // return; // if (index >= Spell.MAX_RUNES_IN_SPELL) // clearFirstIndex(); // if (index > 0 && curTime - castTimes[index - 1] > MAX_TIME_BETWEEN_CASTS) // clearArrays(); spell[index] = r; castTimes[index] = curTime; index++; isValidSpell(p); } private boolean isValidSpell(EntityPlayer p) { printArrays(); Spell sp = SpellsCompendium.isValidSpell(spell, index); if (sp == null) return false; sp.cast(p); clearArrays(); return true; } private void clearFirstIndex() { index = Spell.MAX_RUNES_IN_SPELL - 1; for (int i = 1; i < Spell.MAX_RUNES_IN_SPELL; i--) { spell[i - 1] = spell[i]; castTimes[i - 1] = castTimes[i]; } } private void clearArrays() { index = 0; for (int i = 0; i < Spell.MAX_RUNES_IN_SPELL; i++) { spell[i] = Runes.EMPTY; castTimes[i] = 0; } } private void printArrays() { for (int i = 0; i < Spell.MAX_RUNES_IN_SPELL; i++) { System.out.print(Runes.getInt(spell[i]) + " "); } System.out.println("\n" + index); } @Override public int[] getArray() { int[] ret = new int[1 + index]; ret[0] = index; for (int i = 1; i < ret.length; i++) { ret[i] = Runes.getInt(spell[i - 1]); } return null; } @Override public void setArray(int[] set) { index = set[0]; for (int i = 1; i < set.length; i++) { spell[i - 1] = Runes.getRune(set[i]); } } } [/spoiler] [spoiler=The capability registration] public class CapabilitySpellCasting { @CapabilityInject(ISpellCasting.class) public static final Capability<ISpellCasting> SPELL_CASTING_CAPABILITY = null; public static void register() { CapabilityManager.INSTANCE.register(ISpellCasting.class, new Storage(), DefaultImplementation.class); } public static class Storage implements IStorage<ISpellCasting> { @Override public NBTBase writeNBT(Capability<ISpellCasting> capability, ISpellCasting instance, EnumFacing side) { return new NBTTagIntArray(instance.getArray()); } @Override public void readNBT(Capability<ISpellCasting> capability, ISpellCasting instance, EnumFacing side, NBTBase nbt) { instance.setArray(((NBTTagIntArray)nbt).getIntArray()); } } } [spoiler=The capability provider] public class ProviderSpellCasting implements ICapabilityProvider { @Override public boolean hasCapability(Capability<?> capability, EnumFacing facing) { return CapabilitySpellCasting.SPELL_CASTING_CAPABILITY != null && capability == CapabilitySpellCasting.SPELL_CASTING_CAPABILITY; } @Override public <T> T getCapability(Capability<T> capability, EnumFacing facing) { if (hasCapability(capability, facing)) return CapabilitySpellCasting.SPELL_CASTING_CAPABILITY .<T> cast(CapabilitySpellCasting.SPELL_CASTING_CAPABILITY.getDefaultInstance()); return null; } } [spoiler=Calling the capability] public static class Handler implements IMessageHandler<SpellMessage, IMessage> { @Override public IMessage onMessage(SpellMessage message, MessageContext ctx) { System.out .println("Got Spell Packet from: " + message.player + " with rune: " + Runes.getInt(message.rune)); World world = ctx.getServerHandler().playerEntity.worldObj; EntityPlayer player = world.getPlayerEntityByName(message.player); ISpellCasting cap = player.getCapability(CapabilitySpellCasting.SPELL_CASTING_CAPABILITY, null); cap.addRune(player, message.rune); return null; } } I don't need to store the capability between different world loadings, so the serializable interface isn't something that I spent much time looking into. Could that be the issue? Any help would be appreciated. "Beware of bugs in the above code; I have only proved it correct, not tried it." - Donald Knuth
June 2, 20169 yr 1. No, you don't need to store Capability. 2. In packets client->server that are "acting upon" player who issending them, you don't need to send player. * Server knows who sent a packet to it. * ctx.getServerHandler().playerEntity * Only thing you need is rune pressed. 3. Your code is not thread safe: http://greyminecraftcoder.blogspot.com.au/2015/01/thread-safety-with-network-messages.html * Whole thing in your handler should be in runnable. 4. I didn't look for Java mistakes, I will if above will not fix the problem. 1.7.10 is no longer supported by forge, you are on your own.
June 2, 20169 yr When you attach a capability using AttachCapabilitiesEvent or Item#initCapabilities , it will only be saved to NBT if the ICapabilityProvider also implements INBTSerializable . Forge provides the ICapabilitySerializable interface, which simply extends ICapabilityProvider and INBTSerializable . 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.
June 2, 20169 yr Author Alright, I have modified the message handler to be thread safe and removed the unnecessary player variable, thanks for those tips Ernio. I also changed my capability provider to implement ICapabilitySerializable. Unfortunately, I am still running into the same problem: when I send a rune to the server, the console prints out the rune array with only the first index filled. I know that the index is being incremented, so every time that I send a packet it seems to be interacting with a new DefaultImplementation object. [spoiler=Default Implementation] public class DefaultImplementation implements ISpellCasting { private Runes[] spell = new Runes[spell.MAX_RUNES_IN_SPELL]; private long[] castTimes = new long[spell.MAX_RUNES_IN_SPELL]; private int index; private static final float MAX_TIME_BETWEEN_CASTS = 3 * 1000; public DefaultImplementation() { for (int i = 0; i < Spell.MAX_RUNES_IN_SPELL; i++) { spell[i] = Runes.EMPTY; castTimes[i] = 0; } index = 0; } @Override public void addRune(EntityPlayer p, Runes r) { printArrays(); long curTime = System.currentTimeMillis(); // if (index < 0) // return; // if (index >= Spell.MAX_RUNES_IN_SPELL) // clearFirstIndex(); // if (index > 0 && curTime - castTimes[index - 1] > MAX_TIME_BETWEEN_CASTS) // clearArrays(); spell[index] = r; castTimes[index] = curTime; index++; isValidSpell(p); } private boolean isValidSpell(EntityPlayer p) { printArrays(); Spell sp = SpellsCompendium.isValidSpell(spell, index); if (sp == null) return false; sp.cast(p); clearArrays(); return true; } private void clearFirstIndex() { index = Spell.MAX_RUNES_IN_SPELL - 1; for (int i = 1; i < Spell.MAX_RUNES_IN_SPELL; i--) { spell[i - 1] = spell[i]; castTimes[i - 1] = castTimes[i]; } } private void clearArrays() { index = 0; for (int i = 0; i < Spell.MAX_RUNES_IN_SPELL; i++) { spell[i] = Runes.EMPTY; castTimes[i] = 0; } } private void printArrays() { for (int i = 0; i < Spell.MAX_RUNES_IN_SPELL; i++) { System.out.print(Runes.getInt(spell[i]) + " "); } System.out.println("\n" + index); } private int[] runesToIntArray() { int[] ret = new int[spell.MAX_RUNES_IN_SPELL]; for (int i = 0; i < spell.length; i ++) { ret[i] = Runes.getInt(spell[i]); } return ret; } private Runes[] intToRunesArray(int[] ary) { Runes[] ret = new Runes[spell.MAX_RUNES_IN_SPELL]; for (int i = 0; i < ary.length; i ++) { ret[i] = Runes.getRune(ary[i]); } return ret; } @Override public NBTTagCompound getNBT() { NBTTagCompound tag = new NBTTagCompound(); tag.setInteger("index", index); tag.setIntArray("spell", runesToIntArray()); return tag; } @Override public void setNBT(NBTTagCompound comp) { index = comp.getInteger("index"); spell = intToRunesArray(comp.getIntArray("spell")); } } [spoiler=Registration and storage for the capability] public class CapabilitySpellCasting { @CapabilityInject(ISpellCasting.class) public static final Capability<ISpellCasting> SPELL_CASTING_CAPABILITY = null; public static void register() { CapabilityManager.INSTANCE.register(ISpellCasting.class, new Storage(), DefaultImplementation.class); } public static class Storage implements IStorage<ISpellCasting> { @Override public NBTTagCompound writeNBT(Capability<ISpellCasting> capability, ISpellCasting instance, EnumFacing side) { return instance.getNBT(); } @Override public void readNBT(Capability<ISpellCasting> capability, ISpellCasting instance, EnumFacing side, NBTBase nbt) { instance.setNBT((NBTTagCompound)nbt); } } } [spoiler=The new provider code] public class ProviderSpellCasting implements ICapabilitySerializable<NBTTagCompound> { ISpellCasting instance = CapabilitySpellCasting.SPELL_CASTING_CAPABILITY.getDefaultInstance(); @Override public boolean hasCapability(Capability<?> capability, EnumFacing facing) { return CapabilitySpellCasting.SPELL_CASTING_CAPABILITY != null && capability == CapabilitySpellCasting.SPELL_CASTING_CAPABILITY; } @Override public <T> T getCapability(Capability<T> capability, EnumFacing facing) { if (hasCapability(capability, facing)) return CapabilitySpellCasting.SPELL_CASTING_CAPABILITY .<T> cast(CapabilitySpellCasting.SPELL_CASTING_CAPABILITY.getDefaultInstance()); return null; } @Override public NBTTagCompound serializeNBT() { return (NBTTagCompound) CapabilitySpellCasting.SPELL_CASTING_CAPABILITY.getStorage().writeNBT(CapabilitySpellCasting.SPELL_CASTING_CAPABILITY, instance, null); } @Override public void deserializeNBT(NBTTagCompound nbt) { CapabilitySpellCasting.SPELL_CASTING_CAPABILITY.getStorage().readNBT(CapabilitySpellCasting.SPELL_CASTING_CAPABILITY, instance, null, nbt); } } [spoiler=The new message handler code] public static class Handler implements IMessageHandler<SpellMessage, IMessage> { @Override public IMessage onMessage(final SpellMessage message, final MessageContext ctx) { System.out .println("Got Spell Packet from: " + message.player + " with rune: " + Runes.getInt(message.rune)); ctx.getServerHandler().playerEntity.getServer().addScheduledTask(new Runnable() { public void run() { addMessageRune(ctx.getServerHandler().playerEntity, message.rune); } }); return null; } public void addMessageRune(EntityPlayer player, Runes rune) { ISpellCasting cap = player.getCapability(CapabilitySpellCasting.SPELL_CASTING_CAPABILITY, null); cap.addRune(player, rune); } } Could the problem be in my provider's getCapability? That seems correct to me, but I wouldn't be shocked if I was wrong. "Beware of bugs in the above code; I have only proved it correct, not tried it." - Donald Knuth
June 2, 20169 yr Could the problem be in my provider's getCapability? That seems correct to me, but I wouldn't be shocked if I was wrong. That is exactly the issue. Capability#getDefaultInstance creates a new instance of the default implementation. Your getCapability method is creating and returning a new instance every time it's called instead of using the instance you stored in the instance field. 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.
June 2, 20169 yr Author That solved it, thanks Choonster. "Beware of bugs in the above code; I have only proved it correct, not tried it." - Donald Knuth
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.