Skip to main content
LiquidBounce uses a sophisticated hierarchical configuration system built on top of Gson for JSON serialization. The system manages all module settings, user preferences, and persistent data.

Architecture Overview

The configuration system is centered around the ConfigSystem object which manages all registered configs and provides serialization/deserialization functionality.

Core Components

ConfigSystem (config/ConfigSystem.kt:46) The main entry point for configuration management:
object ConfigSystem {
    const val KEY_PREFIX = "liquidbounce"
    
    val rootFolder: File  // LiquidBounce config directory
    val userConfigsFolder: File  // User-created configs
    val configs: ArrayList<Config>  // All registered configs
}
Value (config/types/Value.kt:63) The base class for all configurable values:
open class Value<T : Any>(
    val name: String,
    val aliases: List<String> = emptyList(),
    private var defaultValue: T,
    val valueType: ValueType
)
ValueGroup Groups related values together in a hierarchy. Modules and features extend this to organize their settings.

Configuration Hierarchy

Configs follow a tree structure:
ConfigSystem
├── Module Configs
│   ├── ModuleKillAura
│   │   ├── range: FloatValue
│   │   ├── rotations: BoolValue
│   │   └── targetSettings: ValueGroup
│   │       ├── players: BoolValue
│   │       └── monsters: BoolValue
│   └── ModuleFly
│       └── mode: ModeValueGroup
│           ├── Vanilla
│           └── Jetpack
└── User Configs
    └── Custom presets

Key Features

Serialization

All configs are automatically serialized to JSON:
// Serialize a value group
fun serializeValueGroup(valueGroup: ValueGroup, gson: Gson = fileGson): JsonObject =
    gson.toJsonTree(valueGroup, ValueGroup::class.javaObjectType) as JsonObject

// Deserialize from JSON
fun deserializeValueGroup(valueGroup: ValueGroup, jsonElement: JsonElement)

Backup & Restore

The system supports config backups:
// Create a ZIP backup
ConfigSystem.backup("mybackup")

// Restore from backup
ConfigSystem.restore("mybackup")

Key-Based Access

Values can be accessed via hierarchical keys:
val value = ConfigSystem.findValueByKey("liquidbounce.combat.killaura.range")

Value Types

LiquidBounce supports various value types:
TypeDescriptionExample
BoolValueBoolean on/offEnable/disable features
FloatValueFloating pointRange, speed multipliers
IntValueIntegerTick delays, counts
RangeValueMin/max rangeAttack range 3.0-4.2
ChoiceValueSingle selectionMode selection
TextValueString inputUsernames, messages
ColorValueRGBA colorUI colors
BlockValueBlock typeTarget blocks

Creating Custom Values

class MyModule : ClientModule("MyModule", Category.COMBAT) {
    // Simple boolean
    val enabled by boolean("Enabled", true)
    
    // Float with range
    val speed by float("Speed", 1.0f, 0.1f..5.0f)
    
    // Choice value
    val mode by enumChoice("Mode", Mode.NORMAL)
    
    // Value group
    val targeting by group("Targeting", Category.SETTINGS) {
        val players by boolean("Players", true)
        val monsters by boolean("Monsters", false)
    }
}

Value Listeners

You can attach listeners to react to value changes:
val myValue = float("Speed", 1.0f).onChange { newValue ->
    // Validate and transform
    newValue.coerceIn(0.5f, 2.0f)
}

myValue.onChanged { newValue ->
    // React to changes
    println("Speed changed to $newValue")
}

State Flow Integration

Values expose Kotlin StateFlow for reactive programming:
val speedValue = float("Speed", 1.0f)

launch {
    speedValue.asStateFlow().collect { speed ->
        // React to speed changes
    }
}

Delegated Properties

Values support Kotlin’s delegated properties for clean syntax:
var speed by float("Speed", 1.0f)

fun onTick() {
    if (speed > 2.0f) {
        speed = 2.0f  // Direct assignment
    }
}

File Structure

Configs are stored in .minecraft/LiquidBounce/:
LiquidBounce/
├── accounts.json        # Account manager
├── autosettings.json    # Auto config
├── friends.json         # Friends list
├── modules.json         # Module settings
├── clickgui.json        # GUI positions
├── configs/             # User configs
│   ├── pvp.json
│   └── ghost.json
└── backups/             # Automatic backups
    └── backup_2026-03-03.zip

Gson Adapters

Custom type adapters handle complex types:
  • ColorAdapter - RGBA colors
  • RangeAdapter - Min/max ranges
  • BlockAdapter - Minecraft blocks
  • InputBindAdapter - Key bindings
  • MinecraftAccountAdapter - Account data

Best Practices

Breaking Changes: When renaming values, always add the old name to the aliases list to maintain backward compatibility with existing configs.
// Good - maintains compatibility
val newName by float("NewName", 1.0f, aliases = listOf("OldName"))

// Bad - breaks existing configs
val newName by float("NewName", 1.0f)

Validation

Always validate user input:
val range by float("Range", 4.0f, 1.0f..6.0f).onChange { value ->
    value.coerceIn(1.0f, 6.0f)
}

Immutable Values

Mark values as immutable when they shouldn’t be changed:
val constant by float("Constant", 1.0f).immutable()

Advanced: Auto Configuration

The system supports automatic configuration from remote sources:
object AutoConfig {
    fun loadAutoConfig(metadata: AutoConfigMetadata)
    fun applyConfig(config: JsonObject)
}
This allows for:
  • Server-specific settings
  • Anti-cheat bypass configs
  • Community-shared presets

Thread Safety

Concurrency: Value changes emit events on the calling thread. When modifying values from background threads, ensure proper synchronization.
Values use @Volatile and StateFlow for thread-safe reads, but modifications should be done on the main thread when possible.

Build docs developers (and LLMs) love