# Configuration System

The Hytale server provides a robust configuration system for both server settings and plugin configurations. This guide
covers how to use and extend the configuration system.

## Server Configuration

### HytaleServerConfig

The main server configuration is stored in `HytaleServerConfig`:

```java
HytaleServerConfig config = HytaleServer.get().getConfig();

// Access configuration values
String serverName = config.getServerName();
String motd = config.getMotd();
int maxPlayers = config.getMaxPlayers();
int maxViewRadius = config.getMaxViewRadius();
```

### Server Configuration Structure

```java
HytaleServerConfig {
    // Basic settings
    serverName: String           // Server display name
    motd: String                 // Message of the day
    password: String             // Server password (optional)
    maxPlayers: int              // Maximum player count
    maxViewRadius: int           // Maximum view distance
    localCompressionEnabled: boolean  // Enable packet compression
    
    // Nested configurations
    Defaults {
        world: String            // Default world name
        gameMode: GameMode       // Default game mode
    }
    
    ConnectionTimeouts {
        initialTimeout: Duration
        authTimeout: Duration
        playTimeout: Duration
        joinTimeouts: Map<String, Duration>
    }
    
    RateLimitConfig {
        enabled: boolean
        packetsPerSecond: int
        burstCapacity: int
    }
    
    // Dynamic configurations
    modules: Map<String, Module>
    logLevels: Map<String, Level>
    modConfig: Map<PluginIdentifier, ModConfig>
    
    // Storage providers
    playerStorageProvider: PlayerStorageProvider
    authCredentialStoreConfig: BsonDocument
}
```

## Plugin Configuration

### Defining Plugin Config

Create a configuration class with a Codec:

```java
public class MyPluginConfig {
    
    // Define the codec for serialization
    public static final Codec<MyPluginConfig> CODEC = BuilderCodec.of(MyPluginConfig::new)
        .with("enabled", Codec.BOOLEAN, c -> c.enabled, true)
        .with("maxItems", Codec.INTEGER, c -> c.maxItems, 100)
        .with("welcomeMessage", Codec.STRING, c -> c.welcomeMessage, "Welcome!")
        .with("debugMode", Codec.BOOLEAN, c -> c.debugMode, false)
        .with("spawnLocation", Vector3d.CODEC, c -> c.spawnLocation, new Vector3d(0, 64, 0))
        .build();
    
    // Configuration fields (final for immutability)
    public final boolean enabled;
    public final int maxItems;
    public final String welcomeMessage;
    public final boolean debugMode;
    public final Vector3d spawnLocation;
    
    // Private constructor used by codec
    private MyPluginConfig(boolean enabled, int maxItems, String welcomeMessage, 
                           boolean debugMode, Vector3d spawnLocation) {
        this.enabled = enabled;
        this.maxItems = maxItems;
        this.welcomeMessage = welcomeMessage;
        this.debugMode = debugMode;
        this.spawnLocation = spawnLocation;
    }
}
```

### Using Plugin Config

```java
public class MyPlugin extends JavaPlugin {
    
    private Config<MyPluginConfig> config;
    
    public MyPlugin(JavaPluginInit init) {
        super(init);
        // Initialize config BEFORE setup() is called
        this.config = withConfig(MyPluginConfig.CODEC);
    }
    
    @Override
    protected void setup() {
        MyPluginConfig cfg = config.get();
        
        if (!cfg.enabled) {
            getLogger().info("Plugin is disabled in config");
            return;
        }
        
        getLogger().info("Max items: " + cfg.maxItems);
        getLogger().info("Debug mode: " + cfg.debugMode);
    }
    
    @Override
    protected void start() {
        MyPluginConfig cfg = config.get();
        // Use config values
    }
    
    // Provide access to config for other classes
    public MyPluginConfig getPluginConfig() {
        return config.get();
    }
}
```

### Configuration Storage

Plugin configurations are stored in the server's main config under the plugin identifier:

```json
{
    "modConfig": {
        "com.example:MyPlugin": {
            "enabled": true,
            "maxItems": 100,
            "welcomeMessage": "Welcome!",
            "debugMode": false,
            "spawnLocation": {
                "x": 0.0,
                "y": 64.0,
                "z": 0.0
            }
        }
    }
}
```

## Nested Configurations

### Defining Nested Config

```java
public class AdvancedConfig {
    
    public static final Codec<AdvancedConfig> CODEC = BuilderCodec.of(AdvancedConfig::new)
        .with("general", GeneralConfig.CODEC, c -> c.general, new GeneralConfig())
        .with("features", FeaturesConfig.CODEC, c -> c.features, new FeaturesConfig())
        .with("limits", LimitsConfig.CODEC, c -> c.limits, new LimitsConfig())
        .build();
    
    public final GeneralConfig general;
    public final FeaturesConfig features;
    public final LimitsConfig limits;
    
    private AdvancedConfig(GeneralConfig general, FeaturesConfig features, LimitsConfig limits) {
        this.general = general;
        this.features = features;
        this.limits = limits;
    }
}

public class GeneralConfig {
    public static final Codec<GeneralConfig> CODEC = BuilderCodec.of(GeneralConfig::new)
        .with("name", Codec.STRING, c -> c.name, "Default")
        .with("enabled", Codec.BOOLEAN, c -> c.enabled, true)
        .build();
    
    public final String name;
    public final boolean enabled;
    
    public GeneralConfig() {
        this("Default", true);
    }
    
    private GeneralConfig(String name, boolean enabled) {
        this.name = name;
        this.enabled = enabled;
    }
}
```

## Lists and Maps in Config

### List Configuration

```java
public class ListConfig {
    
    public static final Codec<ListConfig> CODEC = BuilderCodec.of(ListConfig::new)
        .with("allowedPlayers", Codec.STRING.listOf(), c -> c.allowedPlayers, List.of())
        .with("spawnPoints", Vector3d.CODEC.listOf(), c -> c.spawnPoints, List.of())
        .build();
    
    public final List<String> allowedPlayers;
    public final List<Vector3d> spawnPoints;
    
    private ListConfig(List<String> allowedPlayers, List<Vector3d> spawnPoints) {
        this.allowedPlayers = List.copyOf(allowedPlayers);
        this.spawnPoints = List.copyOf(spawnPoints);
    }
}
```

### Map Configuration

```java
public class MapConfig {
    
    public static final Codec<MapConfig> CODEC = BuilderCodec.of(MapConfig::new)
        .with("playerHomes", Codec.mapOf(Codec.STRING, Vector3d.CODEC), c -> c.playerHomes, Map.of())
        .with("warpPoints", Codec.mapOf(Codec.STRING, WarpConfig.CODEC), c -> c.warpPoints, Map.of())
        .build();
    
    public final Map<String, Vector3d> playerHomes;
    public final Map<String, WarpConfig> warpPoints;
    
    private MapConfig(Map<String, Vector3d> playerHomes, Map<String, WarpConfig> warpPoints) {
        this.playerHomes = Map.copyOf(playerHomes);
        this.warpPoints = Map.copyOf(warpPoints);
    }
}
```

## Optional Configuration Values

### Using Optional

```java
public class OptionalConfig {
    
    public static final Codec<OptionalConfig> CODEC = BuilderCodec.of(OptionalConfig::new)
        .with("requiredValue", Codec.STRING, c -> c.requiredValue)
        .withOptional("optionalValue", Codec.STRING, c -> c.optionalValue)
        .build();
    
    public final String requiredValue;
    public final Optional<String> optionalValue;
    
    private OptionalConfig(String requiredValue, Optional<String> optionalValue) {
        this.requiredValue = requiredValue;
        this.optionalValue = optionalValue;
    }
}
```

### Nullable Values with Defaults

```java
.with("value", Codec.STRING, c -> c.value, "default")  // Default if not present
.withOptional("value", Codec.STRING, c -> c.value)     // Optional, may be absent
```

## Configuration Validation

### Custom Validation

```java
public class ValidatedConfig {
    
    public static final Codec<ValidatedConfig> CODEC = BuilderCodec.of(ValidatedConfig::new)
        .with("port", Codec.INTEGER.validate(p -> p > 0 && p < 65536, "Port must be 1-65535"), 
              c -> c.port, 25565)
        .with("name", Codec.STRING.validate(s -> !s.isBlank(), "Name cannot be blank"),
              c -> c.name, "Server")
        .build();
    
    public final int port;
    public final String name;
    
    private ValidatedConfig(int port, String name) {
        this.port = port;
        this.name = name;
    }
}
```

### Range Validation

```java
.with("level", Codec.INTEGER.clamp(1, 100), c -> c.level, 1)
```

## Enum Configuration

```java
public enum Difficulty {
    EASY, NORMAL, HARD, HARDCORE
}

public class GameConfig {
    
    public static final Codec<GameConfig> CODEC = BuilderCodec.of(GameConfig::new)
        .with("difficulty", Codec.enumCodec(Difficulty.class), c -> c.difficulty, Difficulty.NORMAL)
        .build();
    
    public final Difficulty difficulty;
    
    private GameConfig(Difficulty difficulty) {
        this.difficulty = difficulty;
    }
}
```

## Runtime Configuration Access

### Reading Config Values

```java
// In your plugin
MyPluginConfig cfg = config.get();

// Use values
if (cfg.enabled) {
    doSomething(cfg.maxItems);
}
```

### Checking Config Changes

Configuration is typically loaded at startup. For dynamic updates:

```java
// Store reference to check later
private MyPluginConfig lastConfig;

@Override
protected void start() {
    lastConfig = config.get();
}

// Check for changes (if config reload is supported)
public void checkConfigUpdate() {
    MyPluginConfig current = config.get();
    if (!current.equals(lastConfig)) {
        onConfigChanged(lastConfig, current);
        lastConfig = current;
    }
}
```

## Per-Module Configuration

### Module Config Pattern

```java
// Server modules can have their own config sections
Map<String, Module> modules = serverConfig.getModules();

// Get specific module config
Module myModule = modules.get("myModule");
if (myModule != null && myModule.isEnabled()) {
    // Module-specific logic
}
```

## Best Practices

1. **Use immutable configs** - Make config fields `final` and use `List.copyOf()`, `Map.copyOf()`
2. **Provide sensible defaults** - Every config field should have a reasonable default
3. **Validate early** - Use codec validation to catch invalid values at load time
4. **Document config options** - Create documentation for all config options
5. **Use nested configs** - Organize related settings into nested config classes
6. **Keep configs simple** - Don't over-complicate with too many options
7. **Type safety** - Use enums for fixed choices, proper types for values
8. **Test default configs** - Ensure your plugin works with all-default configuration
9. **Handle missing configs** - Gracefully handle when config file doesn't exist
10. **Version your configs** - Consider config versioning for migrations

## Configuration Example

Complete example of a well-structured plugin configuration:

```java
public class ShopPluginConfig {
    
    public static final Codec<ShopPluginConfig> CODEC = BuilderCodec.of(ShopPluginConfig::new)
        .with("enabled", Codec.BOOLEAN, c -> c.enabled, true)
        .with("currency", CurrencyConfig.CODEC, c -> c.currency, new CurrencyConfig())
        .with("shops", ShopsConfig.CODEC, c -> c.shops, new ShopsConfig())
        .with("debug", Codec.BOOLEAN, c -> c.debug, false)
        .build();
    
    public final boolean enabled;
    public final CurrencyConfig currency;
    public final ShopsConfig shops;
    public final boolean debug;
    
    private ShopPluginConfig(boolean enabled, CurrencyConfig currency, 
                             ShopsConfig shops, boolean debug) {
        this.enabled = enabled;
        this.currency = currency;
        this.shops = shops;
        this.debug = debug;
    }
}

public class CurrencyConfig {
    public static final Codec<CurrencyConfig> CODEC = BuilderCodec.of(CurrencyConfig::new)
        .with("name", Codec.STRING, c -> c.name, "Gold")
        .with("symbol", Codec.STRING, c -> c.symbol, "G")
        .with("startingAmount", Codec.INTEGER.clamp(0, 1000000), c -> c.startingAmount, 100)
        .build();
    
    public final String name;
    public final String symbol;
    public final int startingAmount;
    
    public CurrencyConfig() { this("Gold", "G", 100); }
    
    private CurrencyConfig(String name, String symbol, int startingAmount) {
        this.name = name;
        this.symbol = symbol;
        this.startingAmount = startingAmount;
    }
}
```

JSON representation:

```json
{
    "enabled": true,
    "currency": {
        "name": "Gold",
        "symbol": "G",
        "startingAmount": 100
    },
    "shops": {
        "maxItems": 54,
        "allowPlayerShops": true
    },
    "debug": false
}
```
