Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Tutorial · Exlll/ConfigLib Wiki · GitHub
Skip to content
Exlll edited this page Jul 24, 2022 · 16 revisions

Introduction

This tutorial is intended to show most features of this library by going step-by-step through the following example.

Let's say that we want to create a configuration for the following imaginary game:

  • A game of two teams, where one team is only allowed to place blocks and the other is only allowed to break them.
  • Some blocks are not allowed to be placed.
  • The participants in a team can either have a member or a leader role.
  • The participants are described by their UUID, name, and role.
  • The game has a moderator that is described by its UUID, name, and email.
  • The winning team wins a prize while the losers get one of several consolation items.
  • The game takes place in an area.
  • The game can only be played during a specific period (start and end date).
  • Some information should only be used internally and not be written to the configuration file.
  • All fields should be formatted uppercase.

Please note that this is meant to be an example to show most features of this library. You most likely wouldn't want to model a game or configuration like this.

Final configuration

Our final configuration will look like this:

# The game config for our imaginary game!# Valid color codes are: &4, &c, &e# This message is displayed to the winner teamWIN_MESSAGE: '&4YOU WON!'# This message is displayed to the losersLOSE_MESSAGE: '&c...you lost!'FIRST_PRIZE: | ==: org.bukkit.inventory.ItemStack v: 3105 type: DIAMOND_AXE meta: ==: ItemMeta meta-type: UNSPECIFIC enchants: DIG_SPEED: 5 MENDING: 1 DURABILITY: 3CONSOLATION_PRIZES:
- | ==: org.bukkit.inventory.ItemStack v: 3105 type: STICK amount: 2 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: ROTTEN_FLESH amount: 3 - | ==: org.bukkit.inventory.ItemStack v: 3105 type: CARROT amount: 4START_DATE: 2022-01-01END_DATE: 2022-12-31FORBIDDEN_BLOCKS:
- LAVA
- BARRIERMODERATOR:
UUID: 3fc1e4c3-0d6a-4342-a159-4b0fbd78cda8NAME: Mod# The moderators email# It must be valid!EMAIL: mod@admin.comTEAMS:
BLOCK_PLACE:
- UUID: 5621f3b8-cbab-4571-ba97-a4ff3da59b33NAME: EveTEAM_ROLE: LEADER
- UUID: 5b0c7fc3-2da6-48a1-bc06-1b4daa0f6881NAME: DaveTEAM_ROLE: MEMBERBLOCK_BREAK:
- UUID: a4bc6c3e-8159-431d-abde-0b19697d5505NAME: AliceTEAM_ROLE: LEADER
- UUID: e3a2fcdb-a9be-4396-ad43-32a8339220b3NAME: BobTEAM_ROLE: MEMBERARENA:
ARENA_RADIUS: 10# The world and x and z coordinates of the arena.ARENA_CENTER: world;0;0# Authors: Exlll

Steps

1. Create configuration

The first thing we have to do is to create a class and annotate it with @Configuration.

@ConfigurationpublicfinalclassGameConfig {}

2. Add a win and lose message

Then we can add the messages that are displayed to the winning and losing team. Because winMessage and loseMessage are strings, we can just add two fields with the same name and annotate them with @Comment.

@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
}

3. Define prizes

Next we define the prizes for the winning and losing team. Since we want to choose a random item for the losing team, we define several items in a list.

@ConfigurationpublicfinalclassGameConfig {
// ...privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
}

4. Define the period in which the game is allowed to be played

The period in which the game is allowed to be played is given by a start and an end date.

@ConfigurationpublicfinalclassGameConfig {
// ...privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
}

5. Add a set of blocks that are not allowed to be placed.

We don't want the users to place lava or barrier blocks which we can identify by their Material type. Material is an enum type, and you can use any Java enum type with this library.

@ConfigurationpublicfinalclassGameConfig {
// ...privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
}

6. Model participants and moderators

A user is defined by their UUID and name. A participant additionally has a role in the team and a moderator an email. To model that, we can create a User class and subclass it. We also need to annotate the User class with @Configuration. However, the subclasses don't need to be annotated.

We can add constructors to initialize these classes. Every configuration must have a default constructor, though, so we have to add one, too. That constructor can be private.

@ConfigurationpublicfinalclassGameConfig {
// ...enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {/* initialize */}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {/* initialize */}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {/* initialize */}
privateModerator() {}
}
}

7. Model the teams and add a moderator

A team is described by its permission and list of participants. We could implement a new class to model that but instead we are going the easy route and will just map the permission to a list of participants:

@ConfigurationpublicfinalclassGameConfig {
// ...privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE, List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
}

NOTE:

You cannot write User moderator = new Moderator(...)! As described in the README, serializers are selected by the type of the field, which in this case is User. That means that if you do this, only the fields of the User class will be written but the email will not.

8. Add arena

Since this library supports Java records, we can easily model our arena as one. The arena is defined by its center, a Location, and radius, an int. We can add both of these directly as record components because Location is one of the Bukkit types that can be serialized of out the box.

@ConfigurationpublicfinalclassGameConfig {
// ...privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
}

Note that records don't need to be annotated with @Configuration. Also note that record components can be commented as well!

9. Add custom Location serializer

Because we don't like how Location is serialized by default, we are going to write a custom serializer for it. We can do so by implementing the Serializer interface.

To identify the center of our arena, we just need the world as well as the x- and z-coordinates.

@ConfigurationpublicfinalclassGameConfig {
// ...staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
}

Now that we defined a custom serializer, it still needs to be added to a ConfigurationProperties object. We are going to do that in the last step.

10. Add internal fields

We also wanted to add some internal fields which should be ignored when the configuration is serialized. One way to do this is to make the fields final, static, transient, or to annotate them with @Ignore. A second approach is to write a custom FieldFilter and add to the ConfigurationProperties object.

A FieldFilter is simply a predicate that takes a Field and returns true when the field should be serialized and false otherwise.

Let's add two internal fields. Will will add a FieldFilter that filters out fields that start with the word internal in the next section.

@ConfigurationpublicfinalclassGameConfig {
// ...privateintinternal1 = 20;
privateStringinternal2 = "30";
}

11. Use GameConfig

With that, our GameConfig is pretty much ready to use. The final step is to configure a YamlConfigurationProperties object and use it to save our config.

Because we want to serialize Bukkit classes in our config, we have to the use ConfigLib.BUKKIT_DEFAULT_PROPERTIES object from the configlib-paper artifact as our starting point. We add a header and footer, change the formatting, and add a field filter. With that we are done.

publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}

Full example

importde.exlll.configlib.Comment;
importde.exlll.configlib.Configuration;
importde.exlll.configlib.Serializer;
importorg.bukkit.Bukkit;
importorg.bukkit.Location;
importorg.bukkit.Material;
importorg.bukkit.World;
importorg.bukkit.enchantments.Enchantment;
importorg.bukkit.inventory.ItemStack;
importjava.time.LocalDate;
importjava.time.Month;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
@ConfigurationpublicfinalclassGameConfig {
@Comment("This message is displayed to the winner team")
privateStringwinMessage = "&4YOU WON!";
@Comment("This message is displayed to the losers")
privateStringloseMessage = "&c...you lost!";
privateItemStackfirstPrize = initFirstPrize();
privateList<ItemStack> consolationPrizes = List.of(
newItemStack(Material.STICK, 2),
newItemStack(Material.ROTTEN_FLESH, 3),
newItemStack(Material.CARROT, 4)
);
privateLocalDatestartDate = LocalDate.of(2022, Month.JANUARY, 1);
privateLocalDateendDate = LocalDate.of(2022, Month.DECEMBER, 31);
privateSet<Material> forbiddenBlocks = Set.of(Material.LAVA, Material.BARRIER);
privateModeratormoderator = newModerator(UUID.randomUUID(), "Mod", "mod@admin.com");
privateMap<Permission, List<Participant>> teams = Map.of(
Permission.BLOCK_BREAK,
List.of(
newParticipant(UUID.randomUUID(), "Alice", Role.LEADER),
newParticipant(UUID.randomUUID(), "Bob", Role.MEMBER)
),
Permission.BLOCK_PLACE,
List.of(
newParticipant(UUID.randomUUID(), "Eve", Role.LEADER),
newParticipant(UUID.randomUUID(), "Dave", Role.MEMBER)
)
);
privateArenaarena = newArena(10, newLocation(Bukkit.getWorld("world"), 0, 0, 0));
privateintinternal1 = 20;
privateStringinternal2 = "30";
privateItemStackinitFirstPrize() {
ItemStackstack = newItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
returnstack;
}
enumRole {MEMBER, LEADER}
@ConfigurationpublicstaticclassUser {
privateUUIDuuid;
privateStringname;
publicUser(UUIDuuid, Stringname) {
this.uuid = uuid;
this.name = name;
}
privateUser() {}
}
publicstaticfinalclassParticipantextendsUser {
privateRoleteamRole;
publicParticipant(UUIDuuid, Stringname, RoleteamRole) {
super(uuid, name);
this.teamRole = teamRole;
}
privateParticipant() {}
}
publicstaticfinalclassModeratorextendsUser {
@Comment({"The moderators email", "It must be valid!"})
privateStringemail;
publicModerator(UUIDuuid, Stringname, Stringemail) {
super(uuid, name);
this.email = email;
}
privateModerator() {}
}
enumPermission {BLOCK_BREAK, BLOCK_PLACE}
staticfinalclassLocationStringSerializerimplementsSerializer<Location, String> {
@OverridepublicStringserialize(Locationlocation) {
StringworldName = location.getWorld().getName();
intblockX = location.getBlockX();
intblockZ = location.getBlockZ();
returnworldName + ";" + blockX + ";" + blockZ;
}
@OverridepublicLocationdeserialize(Strings) {
String[] split = s.split(";");
Worldworld = Bukkit.getWorld(split[0]);
intx = Integer.parseInt(split[1]);
intz = Integer.parseInt(split[2]);
returnnewLocation(world, x, 0, z);
}
}
recordArena(
intarenaRadius,
@Comment("The world and x and z coordinates of the arena.")
LocationarenaCenter
) {}
// GETTERS ...
}
importde.exlll.configlib.ConfigLib;
importde.exlll.configlib.NameFormatters;
importde.exlll.configlib.YamlConfigurationProperties;
importde.exlll.configlib.YamlConfigurations;
importorg.bukkit.Location;
importorg.bukkit.plugin.java.JavaPlugin;
importjava.io.File;
importjava.nio.file.Path;
publicfinalclassGamePluginextendsJavaPlugin {
@OverridepublicvoidonEnable() {
YamlConfigurationPropertiesproperties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
.header(
""" The game config for our imaginary game! Valid color codes are: &4, &c, &e """
)
.footer("Authors: Exlll")
.addSerializer(Location.class, newGameConfig.LocationStringSerializer())
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.setFieldFilter(field -> !field.getName().startsWith("internal"))
.build();
PathconfigFile = newFile(getDataFolder(), "config.yml").toPath();
GameConfigconfig = YamlConfigurations.update(
configFile,
GameConfig.class,
properties
);
System.out.println(config.getWinMessage());
System.out.println(config.getLoseMessage());
System.out.println(config.getArena());
}
}