Use the Data API for custom player data

Hi!

I’m working on a battle royale plugin I started months ago. I was used to create a class called PlayerData that was holding some data as attributes (accessible via getters and setters) but I think now it is a bad practice. The PlayerData and Players are linked via a map in the main class :innocent:

I read about the doc but I don’t really understand it… And the custom data holders part is empty :stuck_out_tongue:

So a DataHolder is a class that holds data and do nothing more! Fine but I can’t figure out what it does mean… Do you have any concrete example I could read and understand?

I also have trouble understanding others parts of custom data…

If you know any plugin source code that can help me to figure this out I would be very happy :joy:

Thanks for reading and helping me :wink:

This is the gold standard for custom data. It has two examples fully custom data and boolean custom data and there is no other classes in the example. It was uploaded by @pie_flavor so you know who to thank

I guess you were talking about this? Thanks a lot I’ll read this closely. :slight_smile:

Whops sorry, forgot to paste the link. But you found it anyway.

Well I tried to use the DataManipulatorGenerator from @pie_flavor but I still doesn’t succeed to make it works.

I get a lot of errors every time I try to use player.offer(MyCustomKeys.KEY, value) or player.get(MyCustomKeys.KEY).

[13:31:09 ERROR] [Sponge]: Error occurred while executing command 'brp role get' for source EntityPlayerMP['Gunnolfson'/264, l='world', x=-509,50, y=92,00, z=402,50]: null
java.lang.NullPointerException: null
	at java.util.Objects.requireNonNull(Objects.java:203) ~[?:1.8.0_222]
	at java.util.Optional.<init>(Optional.java:96) ~[?:1.8.0_222]
	at java.util.Optional.of(Optional.java:108) ~[?:1.8.0_222]
	at org.spongepowered.api.data.manipulator.mutable.common.AbstractData.get(AbstractData.java:157) ~[AbstractData.class:1.12.2-7.1.6]
	at net.minecraft.entity.Entity.lambda$getCustom$2(SourceFile:3222) ~[vg.class:?]
	at java.util.Optional.flatMap(Optional.java:241) ~[?:1.8.0_222]
	at net.minecraft.entity.Entity.getCustom(SourceFile:3222) ~[vg.class:?]
	at net.minecraft.entity.Entity.get(SourceFile:2930) ~[vg.class:?]
	at io.github.alexandregerault.battleroyale.commands.role.GetRoleCommand.execute(GetRoleCommand.java:29) ~[GetRoleCommand.class:?]
	at org.spongepowered.api.command.args.ChildCommandElementExecutor.execute(ChildCommandElementExecutor.java:255) ~[ChildCommandElementExecutor.class:1.12.2-7.1.6]
	at org.spongepowered.api.command.args.ChildCommandElementExecutor.execute(ChildCommandElementExecutor.java:255) ~[ChildCommandElementExecutor.class:1.12.2-7.1.6]
	at org.spongepowered.api.command.spec.CommandSpec.process(CommandSpec.java:388) ~[CommandSpec.class:1.12.2-7.1.6]
	at org.spongepowered.api.command.dispatcher.SimpleDispatcher.process(SimpleDispatcher.java:340) ~[SimpleDispatcher.class:1.12.2-7.1.6]
	at org.spongepowered.common.command.SpongeCommandManager.process(SpongeCommandManager.java:337) ~[SpongeCommandManager.class:1.12.2-7.1.6]
	at net.minecraft.command.ServerCommandManager.func_71556_a(SourceFile:1156) ~[dh.class:?]
	at net.minecraft.network.NetHandlerPlayServer.func_147361_d(SourceFile:855) ~[pa.class:?]
	at net.minecraft.network.NetHandlerPlayServer.func_147354_a(SourceFile:842) ~[pa.class:?]
	at net.minecraft.network.play.client.CPacketChatMessage.func_148833_a(SourceFile:37) ~[la.class:?]
	at net.minecraft.network.play.client.CPacketChatMessage.func_148833_a(SourceFile:9) ~[la.class:?]
	at org.spongepowered.common.event.tracking.phase.packet.PacketPhaseUtil.onProcessPacket(PacketPhaseUtil.java:193) ~[PacketPhaseUtil.class:1.12.2-7.1.6]
	at net.minecraft.network.PacketThreadUtil$1.redirect$onProcessPacket$zlg000(SourceFile:539) ~[hv$1.class:?]
	at net.minecraft.network.PacketThreadUtil$1.run(SourceFile:13) ~[hv$1.class:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:1.8.0_222]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:1.8.0_222]
	at net.minecraft.util.Util.func_181617_a(SourceFile:46) ~[h.class:?]
	at org.spongepowered.common.SpongeImplHooks.onUtilRunTask(SpongeImplHooks.java:307) ~[SpongeImplHooks.class:1.12.2-7.1.6]
	at net.minecraft.server.MinecraftServer.redirect$onRun$zjk000(SourceFile:4130) ~[MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.func_71190_q(SourceFile:1581) ~[MinecraftServer.class:?]
	at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(SourceFile:349) ~[nz.class:?]
	at net.minecraft.server.MinecraftServer.func_71217_p(SourceFile:560) ~[MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.run(SourceFile:464) ~[MinecraftServer.class:?]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_222]

The code of this command is the following

package io.github.alexandregerault.battleroyale.commands.role;

import io.github.alexandregerault.battleroyale.main.PlayerRoles;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColors;

import io.github.alexandregerault.battleroyale.data.PlayerKeys;

public class GetRoleCommand implements CommandExecutor {

	
	public GetRoleCommand() {}
	
	@Override
	public CommandResult execute(CommandSource src, CommandContext args) throws CommandException {
		if (!(src instanceof Player)) {
			src.sendMessage(Text.of(TextColors.RED, "Player command only"));
			return CommandResult.success();
		}
		
		Player pl = (Player) src;
		
		pl.sendMessage(Text.of(TextColors.GREEN, "Your role is: " + pl.get(PlayerKeys.ROLE).get().name()));
		return CommandResult.success();
	}

}

What I had tried is

@Listener
public void onPlayerConnect(ClientConnectionEvent.Join event) {
	if (!plugin.state().equals(GameStates.LOBBY)) {
		Player player = event.getTargetEntity();
		player.kick(Text.of(TextColors.RED, "A game is already in progress, sorry"));
	} else {
		Player player = (Player) event.getSource();
		player.offer(player.getOrCreate(ClipboardData.class).get());
		player.offer(player.getOrCreate(PlayerData.class).get());
		player.offer(PlayerKeys.ROLE, PlayerRoles.FIGHTER);
	}
}

But it does not work too…

So I wonder if someone can explain me what happens and how we should implement custom data :slight_smile:

You can find the whole sources on this GitHub repository.

The PlayerData being queried has a null role. Step one is figuring out why this is the case. If it is meant to be nullable, the field should be marked optional = true in the generator file.