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:
| Type | Description | Example |
|---|
BoolValue | Boolean on/off | Enable/disable features |
FloatValue | Floating point | Range, speed multipliers |
IntValue | Integer | Tick delays, counts |
RangeValue | Min/max range | Attack range 3.0-4.2 |
ChoiceValue | Single selection | Mode selection |
TextValue | String input | Usernames, messages |
ColorValue | RGBA color | UI colors |
BlockValue | Block type | Target 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.