Storing Persistant Data on the Player
Learn how to save and load persistant data using Components
Here is how to store persistent data using a custom component on a Player.
Build your Component Class
Creating a custom component class leverages the Component system made with the ECS architecture in mind. For more information on Components, check out the ECS Guide.
Start by creating your custom component class. This will be extremely similar to how regular components are created, however we'll setup a custom Codec so the server can translate this data to BSON, or the server's internal json encoder.
Each variable you want to store in your component must have its own Codec field defined in the BuilderCodec. For more information on how to create custom Codecs, check out the ECS Codec Guide.
public class YourPlayerData implements Component<EntityStore> {
// define some vars!
private int someInteger;
private String someString;
private Map<String, String> someMap;
public static final BuilderCodec<YourPlayerData> CODEC =
BuilderCodec.builder(YourPlayerData.class, YourPlayerData::new)
.append(new KeyedCodec<>("SomeInteger", Codec.INTEGER),
(data, value) -> data.someInteger = value, // setter
data -> data.someInteger) // getter
.addValidator(Validators.nonNull())
.add()
.append(new KeyedCodec<>("SomeString", Codec.STRING),
(data, value) -> data.someString = value, // setter
data -> data.someString) // getter
.add()
.append(new KeyedCodec<>("SomeMap",
new MapCodec<>(Codec.STRING, HashMap::new, false)),
(data, value) -> data.someMap = value, // setter
data -> data.someMap) // getter
.add()
.build();
// Getters and Setters are for the purpose of this example omitted.
// constructor
public YourPlayerData() {
this.someInteger = 0;
this.someString = "";
this.someMap = new HashMap<>();
}
// copy constructor for cloning
public YourPlayerData(YourPlayerData clone) {
this.someInteger = clone.someInteger;
this.someString = clone.someString;
this.someMap = clone.someMap;
}
@Nonnull
@Override
public Component<EntityStore> clone() {
return new YourPlayerData(this);
}
}Register your Component
Inside your main class's setup() method, register your new Component.
public class YourPlugin extends JavaPlugin {
private ComponentType<EntityStore, YourPlayerData> yourPlayerDataComponent;
public YourPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
@Override
protected void setup(){
this.yourPlayerDataComponent = this.getEntityStoreRegistry().registerComponent(
YourPlayerData.class,
"YourPlayerDataComponent",
YourPlayerData.CODEC
);
}
public ComponentType<EntityStore, YourPlayerData> getYourPlayerDataComponent() {
return this.yourPlayerDataComponent;
}
}Using your Component
To use your newly created component, you can add the Component to a player using the addComponent method, although this only adds it temporarily, meaning when the player/entity leaves the world, the component is removed. You can use putComponent to ensure the component persists between sessions.
// You will need the Ref and Store to apply components
private void someEntryMethod(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store)
{
// Any entity will work, for now we will use the player
Player player = (Player) store.getComponent(ref, Player.getComponentType());
// since were using putComponent, it could be useful to check if it already exists on the player in case we want to update it
if(store.getComponent(ref, YourPlugin.instance().getYourPlayerDataComponent()) != null)
{
// here we implement logic that updates the component
var comp = store.getComponent(ref, YourPlugin.instance().getYourPlayerDataComponent())
// declare public fields or methods in the component class
comp.SomeString = "An Updated Field";
} else {
// Here we put the component
var myCustomComp = new CustomComponent();
// declare public fields or methods in the component class
myCustomComp.SomeString = "Edited String";
// putComponent allows you to insert declared objects
store.putComponent(ref, YourPlugin.instance().getYourPlayerDataComponent(), myCustomComp);
}
}Using your Data
Your component will be stored within the Store<EntityStore> when added to the player. In the provided example,
we'll fetch this data off the player using the ensureAndGetComponent() method, which will add the component to
the player if it does not exist with the default values.
public class IncompleteCustomCommand extends AbstractPlayerCommand {
public IncompleteCustomCommand() {
super("nope", "don't use this command");
}
@Override
protected void execute(
@NonNullDecl CommandContext commandContext,
@NonNullDecl Store<EntityStore> store,
@NonNullDecl Ref<EntityStore> ref,
@NonNullDecl PlayerRef playerRef,
@NonNullDecl World world
) {
YourPlayerData customData = store.ensureAndGetComponent(ref, YourPlugin.instance().getYourPlayerDataComponent());
// ensureAndGetComponent adds the component to the ref, with the default values defined inside your component.
// use your data
}
}And that's it! Your data will now be saved and loaded automatically with the player.
Using custom components to store persistent data on players is a powerful way to maintain state across sessions. By following the steps outlined above, you can easily create, register, and utilize your own data structures within the Hytale ECS framework. This approach ensures that your data is seamlessly integrated with the game's existing systems. For more information about Hytale's Entity Component System visit the Hytale ECS Theory guide.