Skip to content

Configuration

Petrus Pradella edited this page Jul 30, 2026 · 3 revisions

Configuration

EverNifeCore reads and writes YAML configuration through the EveryConfig library (br.com.finalcraft.everyconfig.*). A config file is a Config handle: you open it, seed defaults with inline comments, read and write typed values by path, and save. The framework wraps that library with ConfigFactory, which bakes in a shared, type-aware codec so that framework types (ItemStack, Location, FancyText, cooldowns, the vector families, ...) cross to and from config for free. This page covers the framework-side surface; for the library itself (binding model, comment engine, async back-store), the EveryConfig wiki is the recommended deep dive.

Opening a config

ConfigManager holds the two core files (config.yml and Cooldowns.yml). Your own plugin opens its files through ConfigFactory. The plugin-scoped overload resolves the file under your plugin's data folder and stamps the standard FinalCraft header:

import br.com.finalcraft.evernifecore.config.ConfigFactory;
import br.com.finalcraft.everyconfig.config.Config;

// Resolved under this plugin's data folder, header seeded automatically.
Config config = ConfigFactory.open(ecPluginData, "config.yml");

There are also plain overloads that take a File, Path or file-name String (no data-folder relocation, no header), plus overloads that accept an explicit Codec or a BackStore.Durability. The codec is inferred from the file extension - .yml, .toml, .json and .jsonc are all supported by the shared type-aware registry.

Reading and writing values

Values are addressed by a dotted path. The most useful method is getOrSetValueIfAbsent: it returns the stored value, or writes your default (with optional comment lines) the first time the key is missing. This is how every core setting is declared - see ECSettings for real examples:

String zoneId = config.getOrSetValueIfAbsent(
        "Settings.Time.ZONE_ID_OF_DAY_OF_TODAY",
        ZoneId.systemDefault().getId(),
        "The timezone used for EverNifeCore and its sub-plugins!",
        "You can use GMT zones, for example 'GMT-3' or 'GMT+8'."
);

int refresh = config.getOrSetValueIfAbsent("Settings.PageViewers.REFRESH_TIME", 5);
boolean warn = config.getOrSetValueIfAbsent("Settings.Warn.enabled", true);

Other common accessors mirror the usual config surface:

Call Purpose
getValue(path, Type.class) Read a typed value (any registered type).
setValue(path, value) Write a value; null removes the key.
getInt / getBoolean / getString(path[, default]) Primitive/string reads with optional default.
getKeys(path) The child keys under a section (for iterating a map-shaped node).
getFile() The backing File, or null for an in-memory config.

Because the codec is type-aware, a registered type works everywhere the same way - as a solo value, a field of another POJO, a map value, or a list element:

config.setValue("spawn.icon", myItemStack);            // ItemStack -> config
ItemStack icon = config.getValue("spawn.icon", ItemStack.class);

Comments and the header

Comments come from two places. Inline comments are the trailing varargs on getOrSetValueIfAbsent (shown above). The banner at the top of the file is the header; the plugin-scoped open overload seeds the standard FinalCraft banner, and you can override it with config.setHeader(String[] lines).

Saving

Saving is explicit. There is no periodic auto-save thread on this branch - a config is written when you ask it to be:

  • config.save() - save synchronously.
  • config.saveAsync() - hand the write to EveryConfig's async back-store and return immediately. This is what the framework uses for hot paths (for example the cooldown file writes with saveAsync()).

A common seed-then-save pattern, taken from ECSettings.initialize(), only writes when defaults were actually added:

// ... declare every setting with getOrSetValueIfAbsent ...
if (config.hasNewSeededDefaults()) {
    config.save();
    config.clearNewSeededDefaults();
}

hasNewSeededDefaults() reports whether any getOrSetValueIfAbsent call inserted a missing default during this pass, so a first boot (or a version that adds new keys) writes the file exactly once and an unchanged config is never rewritten.

Custom types

EveryConfig is Jackson-first, so "teaching" the framework a new type is one serializer/deserializer pair registered on ConfigFactory. Register it once during your bootstrap and the whole config surface composes for free. There are three registration styles plus a list-element add-on:

import br.com.finalcraft.evernifecore.config.ConfigFactory;

// Object-shaped: the value becomes a nested map (nested registered types recurse automatically).
ConfigFactory.register(MyData.class).asMap(MyData::toMap, MyData::fromMap);

// Scalar-shaped: the value becomes a single string, and is usable as a map key.
ConfigFactory.register(MyId.class).asString(MyId::toString, MyId::parse);

// Full control: hand Jackson the serializer/deserializer directly.
ConfigFactory.register(MyThing.class).jackson(mySerializer, myDeserializer);

Registration is allowed at any time. A late registration invalidates the shared codec, which is rebuilt lazily on the next open (copy-on-write): configs opened afterwards see the new type; a config already open keeps the codec it captured. The framework registers its built-ins this way - the position/vector family, Cooldown/GenericCooldown and FancyText in ECBuiltinTypes, and the Bukkit types (ItemStack, Location, GUI LayoutIcon) during the platform's registerConfigTypes() hook.

POJOs with lifecycle hooks

For a value you own end to end, the simplest path is a plain Jackson bean (no-arg constructor, accessors) that optionally implements ConfigLifecycle (br.com.finalcraft.everyconfig.binding.ConfigLifecycle) to run postLoad / postSave logic; FCPlayerInventory is a live example. Combined with @JsonAnyGetter, nested bound entities compose without any central registration.

The same type authority also crosses into the EveryDatabase storage layer, so a type that serializes to a config file also serializes into a player-data section unchanged - see PlayerData and PDSections and Storage Backends.

See also

  • Scheduler and Threading - saveAsync runs on EveryConfig's back-store; the framework's own scheduling primitives live here.
  • Localization - locale files are configs too (@FCLocale).
  • Items and NBT - ItemStack is one of the registered config types.
  • PlayerData and PDSections - the same Jackson type authority, persisted via EveryDatabase.
  • Storage Backends - storage.yml, the one config file the framework generates for the admin rather than for a plugin.

Clone this wiki locally