Skip to main content

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:

  • value is a no-argument method on each choice. Its return value is saved into the config field.
  • labelMethod is 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();
}