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.