A comprehensive guide to the Argonath Framework Configuration System.
The Argonath Configuration System provides a robust, flexible framework for managing mod settings, player preferences, and runtime configuration. It supports multiple formats (YAML, JSON, TOML), validation, defaults, and hot reloading.
ConfigurationManager
├── ConfigLoader (YAML, JSON, TOML)
├── ConfigValidator
├── ConfigCache
└── ConfigWatcher (Hot reload)
Configuration
├── ConfigSection
│ ├── Primitive values
│ ├── Nested sections
│ └── Collections
└── ConfigMetadata
public interface Configuration {
// Get values
<T> T get(String path, T defaultValue);
String getString(String path);
int getInt(String path);
boolean getBoolean(String path);
List<String> getStringList(String path);
// Set values
void set(String path, Object value);
// Sections
ConfigSection getSection(String path);
Set<String> getKeys();
// Persistence
void save();
void reload();
}
# config.yml
# Main configuration file for MyMod
# General settings
general:
enabled: true
debug: false
language: "en_US"
auto_save: true
save_interval: 300 # seconds
# Quest settings
quests:
enabled: true
max_active_quests: 10
allow_quest_sharing: true
quest_timeout: 3600 # seconds
# Quest rewards
rewards:
experience_multiplier: 1.0
currency_multiplier: 1.0
bonus_rewards_enabled: true
# Quest display
display:
show_tracker: true
tracker_position: "top_right"
show_objectives: true
show_rewards: true
# NPC settings
npcs:
enabled: true
spawn_radius: 50
max_npcs: 100
respawn_time: 300
# NPC AI
ai:
pathfinding_enabled: true
update_interval: 20 # ticks
detection_range: 15
# UI settings
ui:
theme: "dark"
scale: 1.0
animations_enabled: true
# Window positions (saved automatically)
windows:
quest_tracker:
x: 10
y: 10
width: 300
height: 400
# Database settings
database:
type: "sqlite"
path: "data/quests.db"
auto_backup: true
backup_interval: 86400 # seconds
# Performance settings
performance:
async_saving: true
cache_size: 100
cleanup_interval: 600
# Feature flags
features:
experimental_features: false
beta_quests: false
advanced_npcs: true
{
"general": {
"enabled": true,
"debug": false,
"language": "en_US"
},
"quests": {
"enabled": true,
"max_active_quests": 10,
"rewards": {
"experience_multiplier": 1.0,
"currency_multiplier": 1.0
}
},
"npcs": {
"enabled": true,
"spawn_radius": 50,
"ai": {
"pathfinding_enabled": true,
"update_interval": 20
}
}
}
# config.toml
[general]
enabled = true
debug = false
language = "en_US"
[quests]
enabled = true
max_active_quests = 10
[quests.rewards]
experience_multiplier = 1.0
currency_multiplier = 1.0
[npcs]
enabled = true
spawn_radius = 50
[npcs.ai]
pathfinding_enabled = true
update_interval = 20
import com.argonath.framework.config.*;
public class ConfigurationExample {
private Configuration config;
public void loadConfig() {
// Load from file
config = ConfigurationManager.load("config.yml");
// With defaults
config = ConfigurationManager.loadWithDefaults(
"config.yml",
getDefaultConfig()
);
}
private Configuration getDefaultConfig() {
Configuration defaults = new YamlConfiguration();
defaults.set("general.enabled", true);
defaults.set("general.debug", false);
defaults.set("quests.max_active_quests", 10);
return defaults;
}
public void saveConfig() {
config.save();
}
}
public class ConfigurationBuilder {
public static Configuration createDefaultConfig() {
return new ConfigurationBuilder()
.section("general")
.set("enabled", true)
.set("debug", false)
.set("language", "en_US")
.endSection()
.section("quests")
.set("enabled", true)
.set("max_active_quests", 10)
.section("rewards")
.set("experience_multiplier", 1.0)
.set("currency_multiplier", 1.0)
.endSection()
.endSection()
.build();
}
}
public class TypedConfiguration {
private final Configuration config;
public TypedConfiguration(Configuration config) {
this.config = config;
}
// General settings
public boolean isEnabled() {
return config.getBoolean("general.enabled", true);
}
public void setEnabled(boolean enabled) {
config.set("general.enabled", enabled);
}
public String getLanguage() {
return config.getString("general.language", "en_US");
}
// Quest settings
public int getMaxActiveQuests() {
return config.getInt("quests.max_active_quests", 10);
}
public double getExperienceMultiplier() {
return config.getDouble("quests.rewards.experience_multiplier", 1.0);
}
// Nested configuration
public QuestConfig getQuestConfig() {
ConfigSection section = config.getSection("quests");
return new QuestConfig(section);
}
}
public class QuestConfig {
private final ConfigSection section;
public QuestConfig(ConfigSection section) {
this.section = section;
}
public boolean isEnabled() {
return section.getBoolean("enabled", true);
}
public int getMaxActiveQuests() {
return section.getInt("max_active_quests", 10);
}
public RewardConfig getRewardConfig() {
return new RewardConfig(section.getSection("rewards"));
}
}
Settings that apply to all players.
public class GlobalConfig {
private static final Configuration config =
ConfigurationManager.load("global.yml");
public static boolean isDebugEnabled() {
return config.getBoolean("debug", false);
}
public static int getMaxPlayers() {
return config.getInt("max_players", 100);
}
public static void setDebugEnabled(boolean debug) {
config.set("debug", debug);
config.save();
}
}
Server-specific settings.
public class ServerConfig {
private final Configuration config;
public ServerConfig(String serverId) {
this.config = ConfigurationManager.load(
String.format("servers/%s.yml", serverId)
);
}
public String getServerName() {
return config.getString("name", "Unnamed Server");
}
public int getPort() {
return config.getInt("port", 25565);
}
public List<String> getEnabledMods() {
return config.getStringList("enabled_mods");
}
}
Toggle and configure specific features.
public class FeatureConfig {
private final Configuration config;
public FeatureConfig() {
this.config = ConfigurationManager.load("features.yml");
}
public boolean isFeatureEnabled(String featureName) {
return config.getBoolean("features." + featureName + ".enabled", false);
}
public void enableFeature(String featureName) {
config.set("features." + featureName + ".enabled", true);
config.save();
}
public void disableFeature(String featureName) {
config.set("features." + featureName + ".enabled", false);
config.save();
}
public <T> T getFeatureSetting(String featureName, String setting, T defaultValue) {
return config.get("features." + featureName + "." + setting, defaultValue);
}
}
Quest-specific settings.
public class QuestConfiguration {
public static Quest loadQuestFromConfig(String questId) {
Configuration config = ConfigurationManager.load(
String.format("quests/%s.yml", questId)
);
return new QuestBuilder(questId)
.withName(config.getString("name"))
.withDescription(config.getString("description"))
.withCategory(config.getString("category"))
.setRecommendedLevel(config.getInt("recommended_level", 1))
.withObjectives(loadObjectives(config.getSection("objectives")))
.withRewards(loadRewards(config.getSection("rewards")))
.build();
}
private static List<QuestObjective> loadObjectives(ConfigSection section) {
List<QuestObjective> objectives = new ArrayList<>();
for (String key : section.getKeys()) {
ConfigSection objSection = section.getSection(key);
String type = objSection.getString("type");
QuestObjective objective = switch (type) {
case "collect" -> createCollectObjective(objSection);
case "kill" -> createKillObjective(objSection);
case "talk" -> createTalkObjective(objSection);
default -> null;
};
if (objective != null) {
objectives.add(objective);
}
}
return objectives;
}
private static CollectObjective createCollectObjective(ConfigSection section) {
return new CollectObjective(
section.getString("item"),
section.getInt("amount")
);
}
}
public class PlayerConfigManager {
private final Map<UUID, Configuration> playerConfigs = new ConcurrentHashMap<>();
private final Path configDirectory;
public PlayerConfigManager(Path configDirectory) {
this.configDirectory = configDirectory;
}
public Configuration getPlayerConfig(Player player) {
return playerConfigs.computeIfAbsent(player.getUUID(), uuid -> {
Path configFile = configDirectory.resolve(uuid + ".yml");
return ConfigurationManager.loadOrCreate(configFile.toString());
});
}
public void savePlayerConfig(Player player) {
Configuration config = playerConfigs.get(player.getUUID());
if (config != null) {
config.save();
}
}
public void saveAllPlayerConfigs() {
playerConfigs.values().forEach(Configuration::save);
}
public void unloadPlayerConfig(Player player) {
Configuration config = playerConfigs.remove(player.getUUID());
if (config != null) {
config.save();
}
}
}
public class PlayerSettings {
private final Configuration config;
public PlayerSettings(Player player) {
this.config = PlayerConfigManager.getInstance().getPlayerConfig(player);
}
// UI Settings
public boolean isQuestTrackerEnabled() {
return config.getBoolean("ui.quest_tracker.enabled", true);
}
public void setQuestTrackerEnabled(boolean enabled) {
config.set("ui.quest_tracker.enabled", enabled);
save();
}
public String getQuestTrackerPosition() {
return config.getString("ui.quest_tracker.position", "top_right");
}
// Notification Settings
public boolean areNotificationsEnabled() {
return config.getBoolean("notifications.enabled", true);
}
public NotificationType getNotificationStyle() {
String style = config.getString("notifications.style", "POPUP");
return NotificationType.valueOf(style);
}
// Gameplay Settings
public boolean isAutoAcceptEnabled() {
return config.getBoolean("gameplay.auto_accept_quests", false);
}
public int getMaxTrackedQuests() {
return config.getInt("gameplay.max_tracked_quests", 5);
}
// Keybindings
public String getQuestLogKeybind() {
return config.getString("keybinds.quest_log", "L");
}
public void setQuestLogKeybind(String key) {
config.set("keybinds.quest_log", key);
save();
}
private void save() {
config.save();
}
}
public class PlayerPreferences {
public static class Preference<T> {
private final String key;
private final T defaultValue;
public Preference(String key, T defaultValue) {
this.key = key;
this.defaultValue = defaultValue;
}
public T get(Configuration config) {
return config.get(key, defaultValue);
}
public void set(Configuration config, T value) {
config.set(key, value);
}
}
// Define preferences
public static final Preference<Boolean> QUEST_TRACKER_ENABLED =
new Preference<>("ui.quest_tracker", true);
public static final Preference<Integer> MAX_TRACKED_QUESTS =
new Preference<>("gameplay.max_tracked", 5);
public static final Preference<String> THEME =
new Preference<>("ui.theme", "dark");
// Usage
public static <T> T get(Player player, Preference<T> preference) {
Configuration config = getPlayerConfig(player);
return preference.get(config);
}
public static <T> void set(Player player, Preference<T> preference, T value) {
Configuration config = getPlayerConfig(player);
preference.set(config, value);
config.save();
}
}
public class DynamicConfig {
private final Configuration config;
private final List<ConfigChangeListener> listeners = new ArrayList<>();
public void set(String path, Object value) {
Object oldValue = config.get(path, null);
config.set(path, value);
// Notify listeners
ConfigChangeEvent event = new ConfigChangeEvent(path, oldValue, value);
listeners.forEach(listener -> listener.onConfigChange(event));
}
public void addChangeListener(ConfigChangeListener listener) {
listeners.add(listener);
}
public void addChangeListener(String path, ConfigChangeListener listener) {
listeners.add(event -> {
if (event.getPath().equals(path)) {
listener.onConfigChange(event);
}
});
}
}
public interface ConfigChangeListener {
void onConfigChange(ConfigChangeEvent event);
}
public class ConfigChangeEvent {
private final String path;
private final Object oldValue;
private final Object newValue;
public ConfigChangeEvent(String path, Object oldValue, Object newValue) {
this.path = path;
this.oldValue = oldValue;
this.newValue = newValue;
}
// Getters...
}
public class ConfigWatcher {
private final Path configFile;
private final Configuration config;
private final WatchService watchService;
public ConfigWatcher(Path configFile) throws IOException {
this.configFile = configFile;
this.config = ConfigurationManager.load(configFile.toString());
this.watchService = FileSystems.getDefault().newWatchService();
configFile.getParent().register(
watchService,
StandardWatchEventKinds.ENTRY_MODIFY
);
startWatching();
}
private void startWatching() {
new Thread(() -> {
while (true) {
try {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equals(configFile.getFileName().toString())) {
onConfigFileChanged();
}
}
key.reset();
} catch (InterruptedException e) {
break;
}
}
}).start();
}
private void onConfigFileChanged() {
config.reload();
System.out.println("Configuration reloaded");
}
}
public class ConfigValidator {
public static class ValidationResult {
private final boolean valid;
private final List<String> errors;
public ValidationResult(boolean valid, List<String> errors) {
this.valid = valid;
this.errors = errors;
}
public boolean isValid() {
return valid;
}
public List<String> getErrors() {
return errors;
}
}
public static ValidationResult validate(Configuration config, ConfigSchema schema) {
List<String> errors = new ArrayList<>();
for (ConfigField field : schema.getFields()) {
if (field.isRequired() && !config.contains(field.getPath())) {
errors.add("Required field missing: " + field.getPath());
}
if (config.contains(field.getPath())) {
Object value = config.get(field.getPath(), null);
if (!field.getType().isInstance(value)) {
errors.add(String.format(
"Invalid type for %s: expected %s, got %s",
field.getPath(),
field.getType().getSimpleName(),
value.getClass().getSimpleName()
));
}
if (!field.isValid(value)) {
errors.add("Invalid value for " + field.getPath() + ": " + value);
}
}
}
return new ValidationResult(errors.isEmpty(), errors);
}
}
public class ConfigSchema {
private final List<ConfigField> fields = new ArrayList<>();
public ConfigSchema field(String path, Class<?> type) {
fields.add(new ConfigField(path, type, false));
return this;
}
public ConfigSchema requiredField(String path, Class<?> type) {
fields.add(new ConfigField(path, type, true));
return this;
}
public ConfigSchema fieldWithValidator(String path, Class<?> type, Predicate<Object> validator) {
ConfigField field = new ConfigField(path, type, false);
field.setValidator(validator);
fields.add(field);
return this;
}
public List<ConfigField> getFields() {
return fields;
}
}
public class ConfigField {
private final String path;
private final Class<?> type;
private final boolean required;
private Predicate<Object> validator;
public ConfigField(String path, Class<?> type, boolean required) {
this.path = path;
this.type = type;
this.required = required;
}
public void setValidator(Predicate<Object> validator) {
this.validator = validator;
}
public boolean isValid(Object value) {
return validator == null || validator.test(value);
}
// Getters...
}
public class ConfigValidationExample {
public static void validateQuestConfig() {
Configuration config = ConfigurationManager.load("quest.yml");
ConfigSchema schema = new ConfigSchema()
.requiredField("name", String.class)
.requiredField("description", String.class)
.field("recommended_level", Integer.class)
.fieldWithValidator("max_active_quests", Integer.class,
value -> (int) value > 0 && (int) value <= 20)
.fieldWithValidator("experience_multiplier", Double.class,
value -> (double) value >= 0.1 && (double) value <= 10.0);
ValidationResult result = ConfigValidator.validate(config, schema);
if (!result.isValid()) {
System.err.println("Configuration validation failed:");
result.getErrors().forEach(System.err::println);
}
}
}
public class HotReloadManager {
private final Map<String, Configuration> configs = new ConcurrentHashMap<>();
private final Map<String, List<ReloadListener>> listeners = new ConcurrentHashMap<>();
public void registerConfig(String id, Configuration config) {
configs.put(id, config);
}
public void addReloadListener(String configId, ReloadListener listener) {
listeners.computeIfAbsent(configId, k -> new ArrayList<>()).add(listener);
}
public void reload(String configId) {
Configuration config = configs.get(configId);
if (config != null) {
config.reload();
notifyListeners(configId, config);
}
}
public void reloadAll() {
configs.forEach((id, config) -> {
config.reload();
notifyListeners(id, config);
});
}
private void notifyListeners(String configId, Configuration config) {
List<ReloadListener> configListeners = listeners.get(configId);
if (configListeners != null) {
configListeners.forEach(listener -> listener.onReload(config));
}
}
}
@FunctionalInterface
public interface ReloadListener {
void onReload(Configuration config);
}
public class ReloadCommand {
@Command("config reload")
@Permission("admin.config.reload")
public void reload(CommandSender sender, String configId) {
if (configId.equals("all")) {
HotReloadManager.getInstance().reloadAll();
sender.sendMessage("All configurations reloaded");
} else {
HotReloadManager.getInstance().reload(configId);
sender.sendMessage("Configuration reloaded: " + configId);
}
}
}
public class ConfigMigration {
public static Configuration migrate(Configuration config) {
int version = config.getInt("version", 1);
while (version < CURRENT_VERSION) {
switch (version) {
case 1 -> migrateV1ToV2(config);
case 2 -> migrateV2ToV3(config);
// Add more migrations...
}
version++;
}
config.set("version", CURRENT_VERSION);
return config;
}
private static void migrateV1ToV2(Configuration config) {
// Rename keys
if (config.contains("old_key")) {
config.set("new_key", config.get("old_key", null));
config.remove("old_key");
}
// Add new defaults
config.setDefault("new_feature.enabled", true);
}
}
Check file path, permissions, and format validity.
Verify save() is called and file is writable.
Use proper getters and validate types.
For more information: