Jump to content

[1.9][Solved] Capability isn't storing data between calls


Snoopy_WWI_Ace

Recommended Posts

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

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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.

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.