-
Notifications
You must be signed in to change notification settings - Fork 5
Laying out Configs
Proper layout of a config can make or break user and modder experience in and out of game. Determine the flow of your config settings, how they are or will be grouped into common themes, and create a layout matching expectations for navigation, maintenance, and use.
The first question to ask; how many config files will there be? For small mods, one config class will almost always be enough. For more complex mods, breaking configs into parts can be very beneficial.
- This choice will also affect what the player sees when they open your 'root' config screen.
NOTE: Config registration can be separated into synced and non-synced (client-side) configs. If you have client-only settings, consider splitting your config and putting all client settings together into their own config(s)
For small mods or if you want to be quick about things, create one config class with all settings in one place.
- The screen will open to the config settings screen itself, with all options laid out in a list.
- There will be one .toml file managing all stored data for your config.
Fzzy Config maintains the declaration order of settings when displaying them in-game. As such, best practice is to group similar setting together. This makes it easier for users to navigate the setting list in a sensible manner.
- Don't worry about adding new setting 'in the middle' of a config class later. Fzzy Config will handle the new addition without a hiccup. Focus on keeping your settings organized as your config grows
Fzzy Config makes organizing and partitioning your config settings easy. Create multiple config classes with the same namespace and they will be automatically grouped together in-game.
- The main screen will be a 'config selector', with buttons leading to each individual config screen.
- One .toml file will be generated per config class.
Similar settings should be grouped into configs together. A common user expectation is that configs are grouped by in-game 'elements'.
- One config per registry type: Items config, Blocks config, Enchantments config, etc.
- One config per major gameplay element: Crafting config, Loot config, Client settings, etc.
- Take advantage of class-wide annotations for a particular config like
@WithPerms
/@WithCustomPerms
(in this case, lock some settings in a config that requires particular permissions)
Like single config layouts, items within a config class should be organized in a meaningful manner.
Large configs benefit from being broken up into parts, if separate config classes aren't suitable. Fzzy Config supports organization in three primary ways for two main purposes, sub-configuration and layout flow. Two of these methods also act as anchors (See below).
Strategy | UI Style | Repeatable | Anchor |
---|---|---|---|
Sections | Opens new screen | Yes | Yes |
Objects | Opens popup | Yes | No |
Groups | Collapsible inline grouping | No | Yes |
If you use a common subclass for creation of repeating sub-elements, consider using the @Translation
annotation, which will let you define common lang for all instances of the repeating element.
Subconfig classes can be extended from ConfigSection
. Subconfigs made this way will open a new, separate config screen of their own on top of the parent config screen.
- Sections are recommended for large sections of a parent config that belong together, where separating them into individual configs completely might be laborious.
- For example, if you have an
ItemsConfig
, you may have aSwords
section, anAxes
section, and so on.
// kotlin
class ItemsConfig: Config(Identifier.of(MOD_ID, "items_config")) {
//settings that apply to all items can go in the parent class
var overallItemSetting = true
var overallItemWeight = 10
// category-specific settings can go into sections
var axes = AxesSection() // axe settings are stored here
class AxesSection: ConfigSection() { // a Config Section. Self-serializable, and will add a "layer" to the GUI.
/* Axe-specific settings go here */
}
var swords = SwordsSection() // axe settings are stored here
class SwordsSection: ConfigSection() { // a Config Section. Self-serializable, and will add a "layer" to the GUI.
/* Sword-specific settings go here */
}
var tridents = TridentsSection() // axe settings are stored here
class TridentsSection: ConfigSection() { // a Config Section. Self-serializable, and will add a "layer" to the GUI.
/* Trident-specific settings go here */
}
}
// java
public class ItemsConfig extends Config {
public ItemsConfig() {
super(Identifier.of(MOD_ID, "items_config"));
}
//settings that apply to all items can go in the parent class
public boolean overallItemSetting = true;
public integer overallItemWeight = 10;
// category-specific settings can go into sections
public AxesSection axes = new AxesSection(); // axe settings are stored here
public static class AxesSection extends ConfigSection { // a Config Section. Self-serializable, and will add a "layer" to the GUI.
/* Axe-specific settings go here */
}
public SwordsSection swords = new SwordsSection(); // axe settings are stored here
public static class SwordsSection extends ConfigSection { // a Config Section. Self-serializable, and will add a "layer" to the GUI.
/* Sword-specific settings go here */
}
public TridentsSection tridents = new TridentsSection(); // axe settings are stored here
public static class TridentsSection extends ConfigSection { // a Config Section. Self-serializable, and will add a "layer" to the GUI.
/* Trident-specific settings go here */
}
}
Validation is its own topic, but for the sake of this article, FzzyConfig supports arbitrary Simple Objects (POJO/plain old java objects, POKO/plain old koktlin objects) with its validation engine. Validated Objects (called ValidatedAny
internally) will open a 'popup' window where the user can make selections similarly to a config screen.
- Objects are recommended where you need small, repeating blocks of particular settings that the user may want to copy and paste between settings
- For example, if you have an `EntityConfig, you may have an object per entity that defines common settings like health, damage, movement speed, and so on.
// kotlin
class BoisConfig: Config(Identifier.of(MOD_ID, "bois_config")) {
// If there are common clusters of settings you want to use in many places, such as mob stats,
// you can use ValidatedAny to implement arrangements of settings from one common source
// the empty constructor is needed for serialization
class BoiStats(hp: Double, dmg: Double, spd: Double): Walkable { //this doesn't *have* to implement Walkable, but it enables automatic validation
constructor(): this(20.0, 5.0, 0.3) // empty constructor for serialization and validation
var health = hp
var damage = dmg
var speed = spd
}
//settings built from your generic boi stats object
var bigBoi = ValidatedAny(BoiStats(40.0,8.0,0.15))
var littleBoi = ValidatedAny(BoiStats(10.0,1.0,0.4))
var regularBoi = ValidatedAny(BoiStats())
// you don't need to use ValidatedAny! FzzyConfig knows to wrap objects internally if they implement Walkable
var plainBoi = BoiStats()
}
// java
public class BoisConfig extends Config {
public BoisConfig() {
super(Identifier.of(MOD_ID, "bois_config"));
}
// If there are common clusters of settings you want to use in many places, such as mob stats,
// you can use ValidatedAny to implement arrangements of settings from one common source
// the empty constructor is needed for serialization
public class BoiStats implements Walkable { //this doesn't have to implement Walkable, but it enables automatic validation
public BoiStats() {
this(20.0,5.0,0.3);
}
public BoiStats(double hp, double dmg, double spd) {
this.health = hp;
this.damage = dmg;
this.speed = spd;
}
public double health;
public double damage;
public double speed;
}
//settings built from your generic boi stats object
public ValidatedAny<BoiStats> bigBoi = new ValidatedAny(new BoiStats(40.0,8.0,0.15));
public ValidatedAny<BoiStats> littleBoi = new ValidatedAny(new BoiStats(10.0,1.0,0.4));
public ValidatedAny<BoiStats> regularBoi = new ValidatedAny(new BoiStats());
// you don't need to use ValidatedAny! FzzyConfig knows to wrap objects with one internally if they implement Walkable
public BoiStats plainBoi = new BoiStats();
}
Added as of Fzzy Config 0.6.0. Groups are inline organization elements that don't _structurally* change the config internally in any way. They are only used for visual organization of information in a config settings list, and as an anchor for quick navigation.
Groups are great when further breaking down of sub-configs is tedious to manage in-code, or when simplicity of config layout is desired without sacrificing organization. For example, say you have a Main config and a Client config. You may want to leave the structure this simple internally without needing to worry about calling into specific sub-sections. Groups can still help organize those configs for the player. In the client config there may be a section for GUI element placement, another for sounds and volumes, another for particle effects. The user will be presented with three inline groups of settings that they can collapse out of the way if needed, and can still fast-navigate to with the Go To menu.
Important group concepts
- Groups work like a stack; you push onto the stack by defining a
ConfigGroup
inline with where you want it to start - Groups really work like a stack; to close a group, "Pop" it by annotating the last element you want in that group with
ConfigGroup.Pop
- Groups nest; if you define multiple groups before popping any of them, you will get a nested sub-group.
- Pop for each push; not doing so will lead to undefined behavior. You can pop multiple groups on one setting, both/all groups will end with that multi-popped field.
//kotlin
var group1 = ConfigGroup("test_group") //"Pushes" a new group. Any settings added below this point will be included into the group until it is popped.
var groupSetting1 = TODO()
var groupSetting2 = TODO()
var groupSetting3 = TODO()
@ConfigGroup.Pop //pop the group to close it. Settings below this point won't be part of "test_group"
var groupSettingN = TODO()
var notInGroupSetting = TODO()
//java
public ConfigGroup group1 = new ConfigGroup("test_group"); //"Pushes" a new group. Any settings added below this point will be included into the group until it is popped.
public Object groupSetting1 = TODO();
public Object groupSetting2 = TODO();
public Object groupSetting3 = TODO();
@ConfigGroup.Pop //pop the group to close it. Settings below this point won't be part of "test_group"
public Object groupSettingN = TODO();
public Object notInGroupSetting = TODO();
Anchors are a concept introduced in Fzzy Config 0.6.0. Much like anchors in webpages, anchors are "points of interest" that you can fast-navigate to from within the config GUI. Back in Organization, two of three anchors were introduced. The third is of course configs themselves.
- Configs
- Config Sections
- Groups
As noted above, ValidatedAny
Objects are not anchors.
If your config is laid out with more than one anchor, a Go To...
menu will be available in the bottom left (or by pressing Ctrl + E). Clicking on any item in the resulting popup will "fast travel" the user to the screen and/or place in the screen where the anchor resides.
Configs, sections, and groups all provide mechanisms for customizing anchors. Note in the example image below that all configs, sections, and groups use a predefined icon. You can customize this icon, as well as other things like the label. See the documentation for the specific element you are working on for information on editing the anchor info.