Need Help Implenting Custom Data

So I have a warps plugin, BLWarps for which I am attempting to create custom WarpData for storing in Signs and ItemStacks. Basically, I would like the WarpData to act as a container for a Warp object, which contains a few things like a name and location. I found it most appropriate to extend [AbstractSingleData] (https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/data/manipulator/mutable/common/AbstractSingleData.java), and I have tried to fill in as much as I could figure out from other peopleā€™s code here. It compiles, but doesnā€™t do much more than that. To test out if it works, I print out the result of the transaction offering my WarpData to a Sign, which always returns false.

Any help would be much appreciated

EDIT: Yes, I have seen gabizouā€™s FakeData example, but it has not cleared up much confusion for me.

@gravityfox Since he uses custom data in FoxCore.

I honestly have no idea what iā€™m doing with any of my code. I only know it works.

Letā€™s just say it isnā€™t super easy.

1 Like

You cannot offer a Key unless a DataManipulator using that key is offered first.
Without a DataManipulator, the values cannot be serialized. So you need to offer a new WarpData first so that Sponge knows which keys it may accept for that DataHolder.

I did that (see here), and it threw an NPE:

[13:12:23] [Server thread/INFO] [STDOUT]: [com.blocklaunch.blwarps.eventhandlers.ChangeSignEventHandler:signChange:44]: home
[13:12:23] [Server thread/ERROR] [Sponge]: Could not pass ChangeSignEvent$Impl to Plugin{id=blwarps, name=BLWarps, version=1.2.6}
java.lang.NullPointerException
	at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:213) ~[minecraft_server.1.8.jar:?]
	at org.spongepowered.api.data.manipulator.mutable.common.AbstractData.getValues(AbstractData.java:182) ~[AbstractData.class:1.8-1577-3.1.0-BETA-1025]
	at net.minecraft.tileentity.TileEntity.offerCustom(TileEntity.java:69) ~[bcm.class:?]
	at net.minecraft.tileentity.TileEntity.offer(TileEntity.java:110) ~[bcm.class:?]
	at net.minecraft.tileentity.TileEntity.offer(TileEntity.java:50) ~[bcm.class:?]
	at org.spongepowered.api.data.value.mutable.CompositeValueStore.offer(CompositeValueStore.java:209) ~[CompositeValueStore.class:1.8-1577-3.1.0-BETA-1025]
	at com.blocklaunch.blwarps.eventhandlers.ChangeSignEventHandler.signChange(ChangeSignEventHandler.java:45) ~[ChangeSignEventHandler.class:?]
	at org.spongepowered.common.event.listener.ChangeSignEventListener_ChangeSignEventHandler_signChange5.handle(Unknown Source) ~[?:?]
	at org.spongepowered.common.event.RegisteredListener.handle(RegisteredListener.java:86) ~[RegisteredListener.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.mod.event.SpongeModEventManager.post(SpongeModEventManager.java:233) [SpongeModEventManager.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.mod.event.SpongeModEventManager.post(SpongeModEventManager.java:277) [SpongeModEventManager.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.mod.event.SpongeModEventManager.post(SpongeModEventManager.java:245) [SpongeModEventManager.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.common.SpongeImpl.postEvent(SpongeImpl.java:117) [SpongeImpl.class:1.8-1577-3.1.0-BETA-1025]
	at net.minecraft.network.NetHandlerPlayServer.callSignChangeEvent(NetHandlerPlayServer.java:199) [rj.class:?]
	at net.minecraft.network.NetHandlerPlayServer.func_147343_a(NetHandlerPlayServer.java:1113) [rj.class:?]
	at net.minecraft.network.play.client.C12PacketUpdateSign.func_148833_a(C12PacketUpdateSign.java:51) [mu.class:?]
	at net.minecraft.network.play.client.C12PacketUpdateSign.func_148833_a(C12PacketUpdateSign.java:66) [mu.class:?]
	at net.minecraft.network.PacketThreadUtil$1.onProcessPacket(SourceFile:110) [ih.class:?]
	at net.minecraft.network.PacketThreadUtil$1.run(SourceFile:13) [ih.class:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_45-internal]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_45-internal]
	at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:714) [FMLCommonHandler.class:?]
	at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:656) [MinecraftServer.class:?]
	at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:364) [po.class:?]
	at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:598) [MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:478) [MinecraftServer.class:?]
	at java.lang.Thread.run(Thread.java:745) [?:1.8.0_45-internal]

Just to make sure my Warp wasnā€™t null, I printed out its name (first line in the console output), and it correctly output its name (ā€˜homeā€™).

Any ideas?

1 Like

Whoops, I forgot about thatā€¦ Not entirely sure how Iā€™m supposed to go from a Warp to a Value thoughā€¦

Sponge.getRegistry().getValueFactory().createValue(Keys.WARP, this.getValue());

If there is a default value use the createValue overload method that takes a default

1 Like

Thanks @Saladoc and @simon816

Another thing: Immediately after I successfully offer my WarpData to the Sign, I try to get it back using:

Optional<Warp> checkOptWarp = event.getTargetTile().get(Keys.WARP);
            if(checkOptWarp.isPresent()) {
                System.out.println("present");
                System.out.println(checkOptWarp.get().getName());
            } else {
                System.out.println("not present");
            }

which prints out ā€œnot presentā€ every time. How can I fix this?

Probably caused by the same thing as this: (issue still open)

As of right now @gabizou the great and mighty seems to have fixed this bug.

Itā€™s still open because dataholders other than itemstacks may still have this issue.

Just saying, make absolutely sure that youā€™re registering everything. If Warp is supposed to be DataSerializable, then make it DataSerializable, otherwise the custom data doesnā€™t make sense to be stored to entities/tileentities/itemstacks. Having that said, itā€™s very well possible that thereā€™s some TileEntity bug somewhere that I need to track, but as @gravityfox said earlier, ItemStacks should be working perfectly now.

From what I can tell: you have this line:

warpData = warpData.set(Keys.WARP, (Warp) container.get(Keys.WARP.getQuery()).get());

Which will simply not work with deserialization because Warp isnā€™t a DataSerializable.

At least, as far as I can tell, give this another shot when you fix the above issue.

@gabizou
I was reading this thread, which is basically the same issue as mine. Why would Warp need to implement DataSerializable? If I print out my WarpData#toContainer, I get:

MemoryDataContainer{map={ContentVersion=1, WARP=Warp [position=null, name=home, world=world, x=205.55, y=64.0, z=230.7, groups=null]}}

which seems to be working fine. Granted, I do get this exception:

[17:07:28] [Server thread/ERROR] [FML]: A TileEntity type net.minecraft.tileentity.TileEntitySign has throw an exception trying to write state. It will not persist. Report this to the mod author
java.lang.IllegalArgumentException: Unable to translate object to NBTBase!
	at org.spongepowered.common.util.persistence.NbtTranslator.getBaseFromObject(NbtTranslator.java:166) ~[NbtTranslator.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.common.util.persistence.NbtTranslator.getBaseFromObject(NbtTranslator.java:152) ~[NbtTranslator.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.common.util.persistence.NbtTranslator.containerToCompound(NbtTranslator.java:91) ~[NbtTranslator.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.common.util.persistence.NbtTranslator.containerToCompound(NbtTranslator.java:72) ~[NbtTranslator.class:1.8-1577-3.1.0-BETA-1025]
	at org.spongepowered.common.util.persistence.NbtTranslator.translateData(NbtTranslator.java:285) ~[NbtTranslator.class:1.8-1577-3.1.0-BETA-1025]
	at net.minecraft.tileentity.TileEntity.writeToNbt(TileEntity.java:234) ~[bcm.class:?]
	at net.minecraft.tileentity.TileEntity.onWriteToNBT(TileEntity.java:178) ~[bcm.class:?]
	at net.minecraft.tileentity.TileEntity.func_145841_b(TileEntity.java) ~[bcm.class:?]
	at net.minecraft.tileentity.TileEntitySign.func_145841_b(TileEntitySign.java:35) ~[bdj.class:?]
	at net.minecraft.world.chunk.storage.AnvilChunkLoader.func_75820_a(AnvilChunkLoader.java:382) [bfy.class:?]
	at net.minecraft.world.chunk.storage.AnvilChunkLoader.func_75816_a(AnvilChunkLoader.java:183) [bfy.class:?]
	at net.minecraft.world.gen.ChunkProviderServer.func_73242_b(ChunkProviderServer.java:246) [qs.class:?]
	at net.minecraft.world.gen.ChunkProviderServer.func_73151_a(ChunkProviderServer.java:305) [qs.class:?]
	at net.minecraft.world.WorldServer.func_73044_a(WorldServer.java:938) [qt.class:?]
	at net.minecraft.server.MinecraftServer.func_71267_a(MinecraftServer.java:363) [MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:621) [MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:478) [MinecraftServer.class:?]
	at java.lang.Thread.run(Thread.java:745) [?:1.8.0_45-internal]

If I do have Warp implement DataSerializable, how do I construct a DataContainer? Do I need a Key for each property of the Warp (name, x,y,z coords, etc.)?

When you add an object to a DataContainer, it tries its best to convert it into raw primitive and collection types. It cannot do this with custom data objects without being told how to, so it saves them directly to the tree. While you can see the object because of its toString method, the serializer (in this case for NBT) has no clue how to turn your Warp object into a tree of values.

By making Warp implement DataSerializable, the DataContainer can instead add your object as a tree of values, just like it did for your DataManipulator. This allows for it to be serialized to NBT, JSON, or whatever else.

To answer your question about the containers themselves:

new MemoryDataContainer() - create that, and then serialize your objects in whatever structure you want.

@ZephireNZ @gabizou

Iā€™ve implemented Warp as a DataSerializable and it looks great as far as what I get when I print out its DataContainer, but my output still says that the Warp from Keys.WARP is not present

Iā€™m not certain if theyā€™ll fix the not present, however a couple things I noticed:

  • You havenā€™t made a DataBuilder for Warp - It wasnā€™t mentioned in the other post, but this does the deserialization of the object, make sure you register this with DataManager.

  • WarpData warpData = Preconditions.checkNotNull(mergeFn).merge(copy(), from(dataHolder.toContainer()).orElse(null));
    This will not work as expected. The DataHolder#toContainer() will add your data into a list of all custom datas in the resulting DataContainer, so from() will fail to work. You should get the WarpData object just like you would normally with holder.get(WarpData.class) - that way you have a default object (first param), and an original to use instead if it exists (second object).

  • return Optional.of(set(Keys.WARP, (Warp) container.get(Keys.WARP.getQuery()).orElse(null))); is still incorrect. You need to specifically tell the DataContainer youā€™re wanting to get a serializable object, with container.getSerializable(Key.WARP.getQuery(), Warp.class).

Thanks a million :slightly_smiling:

Iā€™m confused by DataBuilder: thereā€™s a method to build from an existing DataView, but thereā€™s no build method to build from the member variables of the class implementing DataBuilderā€¦ Should I be building from the member variables previously set from DataBuilder#from if the given DataView is null?

DataBuilder is for deserialization. Sponge has just read a tree from a config/NBT and knows that itā€™s a serializable object, but doesnā€™t know how to go from a tree to that object.

So your DataBuilder should take in a DataContainer, read in all the values it needs and create the object.

Make sure that you check if all the values are there that you need - return Optional.empty() otherwise. Throw an error if something doesnā€™t match up (like a Map where a String should be).

In regards to from(T) - you donā€™t need to implement this as far as I know. This is so that it fits the interface of ResettableBuilder.