-
Notifications
You must be signed in to change notification settings - Fork 7
Placeholders
What this page covers: the placeholder engine - Replacer / RegexReplacer and the Manipulator
extension - plus how it bridges to PlaceholderAPI. A RegexReplacer<O> scans a string for closures
(${key}, %key% or {key}), looks each one up against parsers you registered, and substitutes the
result.
There is one such engine in the project. The ${key} placeholders of a Localization message
are this same RegexReplacer, bound to Closures.DOLLAR_CURLY and to a RenderContext - what
changes between the two pages is only the object O the engine resolves against.
import br.com.finalcraft.evernifecore.placeholder.replacer.RegexReplacer;
// A replacer bound to some object type O (here: a Guild).
RegexReplacer<Guild> guildReplacer = new RegexReplacer<Guild>()
.addParser("guild_name", Guild::getName)
.addParser("guild_members", guild -> guild.getMembers().size())
.addParser("guild_level", Guild::getLevel);
String line = guildReplacer.apply("&e%guild_name% &7(lvl %guild_level%, %guild_members% members)", guild);
// -> "&eDragons (lvl 4, 12 members)"Each %key% in the text is resolved by the parser registered under key, passing your object. A
placeholder with no matching parser is left untouched.
A closure is the delimiter pair the engine looks for. Three are built in:
| Closure | Delimiters | Used for |
|---|---|---|
DOLLAR_CURLY |
${key} |
EverNifeCore's canonical form - what every message resolves. |
PERCENT |
%key% |
PlaceholderAPI pass-through, and the default of a bare new RegexReplacer<>(). |
BRACKET |
{key} |
Manipulator templates (see Manipulators). |
Pick one when you build the replacer:
RegexReplacer<Guild> canonical = new RegexReplacer<>(Closures.DOLLAR_CURLY)
.addParser("guild_name", Guild::getName);
canonical.apply("Welcome to ${guild_name}", guild); // -> "Welcome to Dragons"
RegexReplacer<Guild> braces = new RegexReplacer<>(Closures.BRACKET)
.addParser("guild_name", Guild::getName);
braces.apply("Welcome to {guild_name}", guild); // -> "Welcome to Dragons"A replacer always knows which delimiters it speaks - getClosures() gives them back, and
closures.quote("guild_name") writes the token the way that replacer would match it. Key lookup is
case-insensitive; two keys differing only in case throw at registration rather than silently
shadowing each other.
A candidate that no parser answers for is left untouched, and scanning resumes just after its opening
delimiter - so in "100% of %total%" the stray % pairing with the next one does not swallow
%total%, which still gets its turn.
apply(List<String>, O) runs the same substitution over a whole list (e.g. an item lore).
FCRegexReplacers ships ready-made replacers for the common player types, so you rarely start from
scratch:
import br.com.finalcraft.evernifecore.placeholder.FCRegexReplacers;
String s = FCRegexReplacers.PLAYER_DATA.apply("&a%player% &7last seen %player_last_seen%", playerData);FCRegexReplacers.PLAYER_DATA (an RegexReplacer<IPlayerData>) provides:
| Placeholder | Value |
|---|---|
%player% / %player_name%
|
Player name |
%player_uuid% |
Player UUID |
%player_is_online% |
Whether the player is online |
%player_ontime% |
Formatted play time |
%player_last_seen% / %player_last_seen_millis%
|
Last-seen timestamp |
%player_first_seen% / %player_first_seen_millis%
|
First-seen timestamp |
FCRegexReplacers.PLAYER (an RegexReplacer<FPlayer>) provides %player%, %player_name%,
%player_uuid%, %player_isonline%.
RegexReplacer implements a small provider API. The parser you register receives your object and
returns the value (any type - it's stringified):
replacer.addParser("key", obj -> obj.something()); // dynamic value
replacer.addParser("key", "static description", obj -> obj.x()); // + a description (docs/help)
replacer.addParser("static_key", "literal value"); // constant value
replacer.addParser(new String[]{"hp", "health"}, obj -> obj.hp()); // several aliases -> one parserA default parser handles any closure that no named parser matched - useful for a computed family of keys:
replacer.setDefaultParser((obj, key) -> key.startsWith("stat_")
? obj.getStat(key.substring("stat_".length()))
: null); // return null to leave the placeholder untoucheddescribeAll() returns every registered key mapped to its description ("" when undescribed), in
registration order. This is public contract: it is what an integrating plugin reads to show a
user which placeholders it can offer, so a key registered without a description still shows up.
replacer.describeAll().forEach((key, description) ->
sender.sendMessage("&e" + key + " &7- " + description));A Localization message exposes the same thing for its own declarations through
getPlaceholderProvider().describeAll().
A single message often needs several data sources (the player, a guild, a PlaceholderAPI user). A
CompoundReplacer chains multiple RegexReplacers, each bound to its own object, and applies them
in order:
import br.com.finalcraft.evernifecore.placeholder.replacer.CompoundReplacer;
CompoundReplacer compound = FCRegexReplacers.PLAYER_DATA.compound(playerData) // replacer + its object
.appendReplacer(guildReplacer, guild)
.usePAPI(fplayer); // optional, see below
String result = compound.apply("&a%player% of &e%guild_name%: %vault_eco_balance%");A CompoundReplacer is what FancyText and Localization messages accept via
addReplacer(compoundReplacer) - so you can push a fully-composed set of placeholders into a message
in one call. It runs after the message's own ${key} pass, on the same payloads (text, hover and
click value alike).
For placeholders that carry an argument inside the token - %top_kills_1%, %balance_of_Alice% - use
a manipulator. You give a template with a {closure} where the argument sits, and read the
captured value from the context:
guildReplacer.addManipulator("member_rank_{memberName}", (guild, ctx) -> {
String memberName = ctx.getString("{memberName}"); // the captured argument
return guild.rankOf(memberName);
});
guildReplacer.apply("&7Rank: %member_rank_Alice%", guild); // -> "&7Rank: Officer"ctx.getString("{closure}") returns the captured text; ctx.getArgumento("{closure}") wraps it as an
Argumento for typed access. A template may contain multiple closures (kills_{world}_{player}).
📌 Note - manipulators are an advanced tool for a family of placeholders keyed by an argument. For a fixed set of keys, plain
addParser(...)is simpler and faster.
Two independent directions, both routed through the Platform Abstraction (IPlatform):
Consuming other plugins' %papi% placeholders inside your text - add a PAPI user to a
CompoundReplacer, or parse directly:
compound.usePAPI(fplayer); // PAPI runs after your own replacers
// or, one-off:
String parsed = EverNifeCore.getPlatform().parse(fplayer, "&aBalance: %vault_eco_balance%");
boolean papiHere = EverNifeCore.getPlatform().isPAPIPresent();Publishing your own data so other plugins can read it through PAPI - register an integration
under a base identifier; it returns a RegexReplacer you populate, and each parser becomes a
%<baseId>_<key>% PlaceholderAPI placeholder:
RegexReplacer<PlayerData> papi = EverNifeCore.getPlatform()
.createPlaceholderIntegration(ecPluginData, "myplugin", PlayerData.class);
papi.addParser("coins", pd -> pd.getPDSection(Profile.class).join().coins);
// other plugins can now use %myplugin_coins%The integration auto-detects legacy vs modern PlaceholderAPI and registers the hook accordingly; you just add parsers.
-
Localization -
@FCLocalemessages, their${key}declarations, andaddReplacer(...). -
FancyText -
addPlaceholder/addParser/addReplaceron rich text. -
Platform Abstraction -
IPlatform.parse/isPAPIPresent/createPlaceholderIntegration. - Integrations - PlaceholderAPI and the other third-party hooks EverNifeCore ships with.
EverNifeCore · Home · made by Petrus Pradella
Getting Started
Commands & Text
Player Data & Storage
- PlayerData & PDSections
- Accounts
- Storage Backends
- Inline Backends for Plugins
- Legacy Data Migration
- Cooldowns
Config & Minecraft Systems
- Configuration
- Scheduler & Threading
- Items & NBT
- GUI Framework
- Integrations
- Economy
- Version Compatibility
Architecture & Reference