Config System
Plugin configs extend dev.twilite.client.config.Config. Fields are rendered in the configuration panel when they use config annotations.
Field initializers are the default values. The client persists changed values under the plugin config namespace.
Basic Config
package com.example.plugins;
import dev.twilite.client.config.Config;
import dev.twilite.client.config.annotation.component.CheckBox;
import dev.twilite.client.config.annotation.component.TextField;
public class ExampleConfig extends Config {
@CheckBox(
key = "enabled",
label = "Enabled",
tooltip = "Whether the feature should run."
)
private boolean enabled = true;
@TextField(
key = "radius",
label = "Radius",
tooltip = "Distance in tiles.",
placeholder = "8",
digit = true,
required = true
)
private int radius = 8;
public boolean enabled() {
return enabled;
}
public int radius() {
return Math.max(1, radius);
}
}
Attach it to the plugin:
@PluginDescriptor(
name = "Example",
config = ExampleConfig.class
)
public class ExamplePlugin extends Plugin {
}
Components
Available component annotations live under dev.twilite.client.config.annotation.component.
@CheckBox
Use for booleans.
@CheckBox(
key = "loot",
label = "Loot items",
tooltip = "Pick up configured loot."
)
private boolean loot;
@TextField
Use for strings and numbers.
@TextField(
key = "itemIds",
label = "Item IDs",
tooltip = "Comma-separated item IDs.",
placeholder = "ItemId.COINS, ItemId.DEATHRUNE, ItemId.BLOODRUNE"
)
private String itemIds = "";
Set digit = true for numeric-only input.
@ComboBox
Use for enum-like choices.
public enum Mode {
BANK,
DROP,
IGNORE
}
@ComboBox(
key = "mode",
label = "Mode",
tooltip = "How items should be handled.",
type = Mode.class
)
private Mode mode = Mode.BANK;
@DynamicComboBox
Use for choices that are built at runtime, such as saved accounts, profiles, or presets.
The annotated field stores the selected value. The values method returns the available choices. If the choices are objects, use value to choose what gets saved and labelMethod to choose what is shown in the UI.
public class ExampleConfig extends Config {
@DynamicComboBox(
key = "accountId",
label = "Account",
tooltip = "Saved account to use.",
values = "savedAccounts",
value = "id",
labelMethod = "displayName",
emptyLabel = "Select account"
)
private String accountId = "";
public List<GameAccount> savedAccounts() {
return accountStore.accounts();
}
public String accountId() {
return accountId;
}
}
The values method must be a no-argument method on the config and return an array or Iterable.
For object choices:
valueis a no-argument method on each choice. Its return value is saved into the config field.labelMethodis a no-argument method on each choice. Its return value is shown in the combo box.
Prefer storing stable scalar values like IDs or enum names. This keeps JSON configs and quickstart config files portable:
{
"accountId": "c7fe38fb-60ee-4f29-bbb7-d316018e2afa"
}
Avoid storing whole runtime objects in config fields.
@MultiChoice
Use for selecting multiple enum values.
public enum LootMode {
STACKABLES,
ALCHEABLES,
SUPPLIES
}
@MultiChoice(
key = "lootModes",
label = "Loot modes",
tooltip = "Loot categories to enable.",
type = LootMode.class,
min = 1,
max = 3
)
private List<LootMode> lootModes = new ArrayList<>(List.of(LootMode.STACKABLES));
The field must be a concrete enum list matching type, such as List<LootMode>.
Do not use a raw List or a broader type.
@ColorPicker
Use for color fields.
@ColorPicker(
key = "highlight",
label = "Highlight",
tooltip = "Tile highlight color."
)
private int highlight = 0x883fc7ff;
@ActionButton
Use for a button that invokes a no-argument config method.
@ActionButton(
key = "openTool",
label = "Tool",
tooltip = "Open the tool window.",
text = "Open",
method = "openTool"
)
private transient boolean openTool;
public void openTool() {
// Open a frame or panel.
}
Mark button backing fields as transient; they are UI actions, not stored config.
Layout
Layout annotations live under dev.twilite.client.config.annotation.layout.
Use @Section to group fields:
@Section("Looting")
@CheckBox(key = "loot", label = "Loot items")
private boolean loot;
Use @Tab for larger configs:
@Tab("Combat")
@TextField(key = "food", label = "Food ID", digit = true)
private int food = ItemId.SHARK;
Nested Config
Config fields can contain another Config object. This is useful when a reusable settings group has its own annotations.
@Section("Advanced")
private ExampleAdvancedConfig advanced = new ExampleAdvancedConfig();
Use @ConfigList when the config contains a repeatable list of nested config objects.
public class LootRuleConfig extends Config {
@TextField(key = "itemId", label = "Item ID", digit = true)
private int itemId = ItemId.COINS;
@CheckBox(key = "noted", label = "Noted")
private boolean noted;
public int itemId() {
return itemId;
}
public boolean noted() {
return noted;
}
}
public class ExampleConfig extends Config {
@ConfigList(
key = "lootRules",
label = "Loot rules",
tooltip = "Items to handle.",
type = LootRuleConfig.class,
min = 0,
max = 20
)
private List<LootRuleConfig> lootRules = new ArrayList<>();
public List<LootRuleConfig> lootRules() {
return lootRules;
}
}
The list field must use the exact row type declared in type, for example List<LootRuleConfig>.
This is important for JSON config files and quickstart imports because Gson uses the field's generic type during deserialization.
Do not declare config lists as List<Config> or raw List.
Conditional Visibility
Override isShown(String key) when fields should appear only in some modes.
@Override
public boolean isShown(String key) {
if ("food".equals(key)) {
return mode == Mode.COMBAT;
}
return true;
}
The key is the annotation key, not the Java field name.
Saving Config Manually
Most UI changes are saved automatically. If config values are changed from code, call save().
public void reset() {
radius = 8;
save();
}