I am working on a statue plugin for Spigot 1.20.4 (Java) that creates NPC statues. Currently, when you create a statue, you can use a consumer to define what happens when said NPC is right clicked.
Statue.StatueBuilder builder = new Statue.StatueBuilder(entityType)
.setName("Test")
.setOnClick(connectionPlayer -> System.out.println("TEST " + new Random().nextInt()));
Statue statue = builder.build();
The #setOnClick takes in a StatueAction, which is defined as:
public interface StatueAction {
void perform(ConnectionPlayer player);
}
I need to save this data (either yml, json, etc) so when the server restarts, the statues will be spawned again. However, I cant serialize the consumer. Tried with YML and JSON. I understand why, but not how to get around it.
Any ideas on how to save the onClick action to a file? Or if this is not a good approach, any ideas for other approaches I might take to achieve a similar goal? I did look into How to serialize a lambda?, however it seems none of those are working correctly (for me), meaning I must be doing something incorrect.
Here is how i am currently trying to serialize it
StatueAction<ConnectionPlayer> r = (connectionPlayer) -> System.out.println("Hello");
Statue.StatueBuilder builder = new Statue.StatueBuilder(entityType).setName("Test").setOnClick(r);
Statue statue = builder.build();
statue.spawn(player.getLocation());
statue.createDefaultAttribs();
System.out.println(statue.serialize());
And the output is
{"name":"Test","entityType":"ARMOR_STAND","entityUUID":"6c2a484c-3168-4fb8-a3ee-8c2d93bddf1d","textDisplaysID":[],"onClick":{}}
The Statue serialization method is defined as
public static class StatueSerializer implements JsonSerializer<Statue> {
@Override
public JsonElement serialize(Statue statue, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", statue.name);
jsonObject.addProperty("entityType", statue.entityType.name());
jsonObject.addProperty("entityUUID", statue.getEntityUUID().toString());
jsonObject.add("textDisplaysID", jsonSerializationContext.serialize(statue.getTextDisplaysID()));
// Serialize the CustomAction using its own serialization logic
jsonObject.add("onClick", jsonSerializationContext.serialize(statue.getOnClick()));
return jsonObject;
}
}
public String serialize() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Statue.class, new Statue.StatueSerializer())
.create();
return gson.toJson(this);
}
If you only have a static amount of possible actions, then instead of having an interface you could use an enum whose constants represent these actions, and then only serialize the name of the action / the constant name.
This would be similar to how Minecraft itself serializes the clickEvent
and hoverEvent
actions of text components.
Another alternative could be to add a serialize
method to your StatueAction
interface so that implementations can serialize all necessary data. But you would also have to include some identifier then, and have some 'registry' where on deserialization the action class is looked up based on the identifier in the JSON data. See other questions about Gson and polymorphism.
Avoid using Class.forName
for deserialization even if it may seem convenient, because if the JSON data can be influenced by an untrusted user, it would allow them to create instances of arbitrary classes, which could be exploited.