|
| 1 | +--- |
| 2 | +title: "Storing Persistant Data on the Player" |
| 3 | +description: "Learn how to save and load persistant data using Components" |
| 4 | +authors: |
| 5 | + - name: "ArcticDev" |
| 6 | + url: "https://github.com/ArcticRaven" |
| 7 | + - name: "Neil Revin" |
| 8 | + url: "https://itsneil.dev/" |
| 9 | +--- |
| 10 | + |
| 11 | +Here is how to store persistent data using a custom component on a Player. |
| 12 | + |
| 13 | +## Build your Component Class |
| 14 | + |
| 15 | +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](../ecs/hytale-ecs-theory.mdx#components). |
| 16 | + |
| 17 | +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. |
| 18 | + |
| 19 | +<Callout type="info"> |
| 20 | +Each variable you want to store in your component must have its own Codec field defined in the BuilderCodec. |
| 21 | +Since the codec system is lambda based, you provide a setter and getter for each field. |
| 22 | + |
| 23 | +Using complex data types (like Lists, Maps, etc) is possible, but can get tricky. |
| 24 | +Refer to the available Codec types in the `Server.jar/com/hypixel/hytale/codec/` package for more information on what types are available. |
| 25 | + |
| 26 | +Note that the 'key' used in the KeyedCodec must start with a Capital Letter, otherwise it may not serialize properly. |
| 27 | +</Callout> |
| 28 | + |
| 29 | +```java |
| 30 | +public class YourPlayerData implements Component<EntityStore> { |
| 31 | + |
| 32 | + // define some vars! |
| 33 | + private int someInteger; |
| 34 | + private String someString; |
| 35 | + private Map<String, String> someMap; |
| 36 | + |
| 37 | + public static final BuilderCodec<YourPlayerData> CODEC = |
| 38 | + BuilderCodec.builder(YourPlayerData.class, YourPlayerData::new) |
| 39 | + .addField(new KeyedCodec<>("SomeInteger", Codec.INTEGER), |
| 40 | + (data, value) -> data.someInteger = value, // setter |
| 41 | + data -> data.someInteger) // getter |
| 42 | + .addField(new KeyedCodec<>("SomeString", Codec.STRING), |
| 43 | + (data, value) -> data.someString = value, // setter |
| 44 | + data -> data.someString) // getter |
| 45 | + .addField(new KeyedCodec<>("SomeMap", |
| 46 | + new MapCodec<>(Codec.STRING, HashMap::new, false)), |
| 47 | + (data, value) -> data.someMap = value, // setter |
| 48 | + data -> data.someMap) // getter |
| 49 | + .build(); |
| 50 | + |
| 51 | + |
| 52 | + |
| 53 | + // Getters and Setters are for the purpose of this example omitted. |
| 54 | + |
| 55 | + // constructor |
| 56 | + public YourPlayerData() { |
| 57 | + this.someInteger = 0; |
| 58 | + this.someString = ""; |
| 59 | + this.someMap = new HashMap<>(); |
| 60 | + } |
| 61 | + |
| 62 | + // copy constructor for cloning |
| 63 | + public YourPlayerData(YourPlayerData clone) { |
| 64 | + this.someInteger = clone.someInteger; |
| 65 | + this.someString = clone.someString; |
| 66 | + this.someMap = clone.someMap; |
| 67 | + } |
| 68 | + |
| 69 | + @NullableDecl |
| 70 | + @Override |
| 71 | + public Component<EntityStore> clone() { |
| 72 | + return new YourPlayerData(this); |
| 73 | + } |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +## Register your Component |
| 78 | +Inside your main class's `setup()` method, register your new Component. |
| 79 | +
|
| 80 | +```java |
| 81 | +public class YourPlugin extends JavaPlugin { |
| 82 | +
|
| 83 | + private ComponentType<EntityStore, YourPlayerData> yourPlayerDataComponent; |
| 84 | +
|
| 85 | + public YourPlugin(@NonNullDecl JavaPluginInit init) { |
| 86 | + super(init); |
| 87 | + } |
| 88 | +
|
| 89 | + @Override |
| 90 | + protected void setup(){ |
| 91 | + this.yourPlayerDataComponent = this.getEntityStoreRegistry().registerComponent( |
| 92 | + YourPlayerData.class, |
| 93 | + "YourPlayerDataComponent", |
| 94 | + YourPlayerData.CODEC |
| 95 | + ); |
| 96 | + } |
| 97 | +
|
| 98 | + public ComponentType<EntityStore, YourPlayerData> getYourPlayerDataComponent() { |
| 99 | + return this.yourPlayerDataComponent; |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | +
|
| 104 | +## Using your Data |
| 105 | +
|
| 106 | +Your component will be stored within the `Store<EntityStore>` when added to the player. In the provided example, |
| 107 | +we'll fetch this data off the player using the `ensureAndGetComponent()` method, which will add the component to |
| 108 | +the player if it does not exist. |
| 109 | + |
| 110 | +```java |
| 111 | +public class IncompleteCustomCommand extends AbstractPlayerCommand { |
| 112 | + |
| 113 | + public IncompleteCustomCommand() { |
| 114 | + super("nope", "don't use this command"); |
| 115 | + } |
| 116 | + |
| 117 | + @Override |
| 118 | + protected void execute( |
| 119 | + @NonNullDecl CommandContext commandContext, |
| 120 | + @NonNullDecl Store<EntityStore> store, |
| 121 | + @NonNullDecl Ref<EntityStore> ref, |
| 122 | + @NonNullDecl PlayerRef playerRef, |
| 123 | + @NonNullDecl World world |
| 124 | + ) { |
| 125 | + YourPlayerData customData = store.ensureAndGetComponent(ref, YourPlugin.instance().getYourPlayerDataComponent()); |
| 126 | + |
| 127 | + // use your data |
| 128 | + } |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +And that's it! Your data will now be saved and loaded automatically with the player. |
| 133 | +
|
| 134 | +## Conclusion |
| 135 | +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 modding framework. This approach ensures that your data is seamlessly integrated with the game's existing systems, providing a robust solution for managing player-specific information. Happy modding! |
0 commit comments