-
Notifications
You must be signed in to change notification settings - Fork 7
Cooldowns
A cooldown in EverNifeCore is a small timed gate: it remembers when something happened and answers "has enough time passed?". The system is a 2×2 matrix - a cooldown belongs either to the server or to a player, and its reach is either local (this server only) or network (shared across a network on the shared backend).
LOCAL (this server) NETWORK (shared across the network)
SERVER-wide Cooldown.of(id) Cooldown.network(id)
PLAYER-owned PlayerCooldown.of(uuid, id) PlayerCooldown.network(uuid, id)
Every handle is a Cooldown (or the PlayerCooldown subclass). The handle is only a view over a
CooldownEntry; where that entry lives (a config file, a PDSection row, an account row, the shared
server-cooldown collection) is what the four factories choose for you.
Server-wide, this-server-only. Stored in this server's Cooldowns.yml under AllCooldowns.<id> (only
while persistent). Synchronous.
Cooldown eventGate = Cooldown.of("weekly_event");
if (eventGate.isInCooldown()) {
eventGate.warnPlayer(sender); // "you need to wait more ..."
} else {
eventGate.startWith(7 * 24 * 60 * 60); // start a 7-day cooldown (seconds)
eventGate.setPersist(true); // survive a restart
}Server-wide and network-wide: a handle over a row in the shared ec_server_cooldowns collection on the
account backend, so every instance reads and writes the same state. Synchronous (the collection is warm).
When the network storage is not bootstrapped - a single server with no shared backend - it collapses to
Cooldown.of(id): one server is the whole network, so a local cooldown already has network reach.
Cooldown global = Cooldown.network("global_boss");
global.startWith(300); // that's it - the handle is born persistent, so this replicates
global.setPersist(true); // redundant now (born persistent), but harmless if you keep it📌 A server network handle is born persistent. Like
PlayerCooldown.network(...), the handle fromCooldown.network(id)starts already persistent - a network cooldown only means anything if it replicates, so the row reaches storage the moment the cooldown is actually started (never on a bare read). You do not need to callsetPersist(true)on it; the shipped/eccooldown set ... --networkcommand still does, purely for uniformity with the local-cooldown path, and it stays harmless.
Owned by one player, this server only: a handle over the player's PlayerCooldownsLocal
PDSection row. Async, because the row may have to be read from the
backend on a cache miss; for an online player the row is hot-loaded at login, so .join() is a cache
hit and safe on the main thread.
PlayerCooldown.of(uuid, "kit_daily").thenAccept(cd -> {
if (!cd.isInCooldown()) {
cd.startWith(24 * 60 * 60).setPersist(true);
}
});Like the server-local one, not persistent by default - call setPersist(true) to outlive a restart.
Owned by the player's account and seen by every instance: a handle over the account-shared
PlayerCooldownsNetwork account section row. Async, same as above, and born persistent -
a network cooldown only means anything if it replicates, so the row grows the moment it is actually
started (never on a bare read).
IPlayerData#getCooldown(identifier) is the convenience shortcut for the player-local route:
playerData.getCooldown("home").thenAccept(cd -> { /* ... */ }); // == PlayerCooldown.of(uuid, "home")For an offline target, bridge the async result back with
PlayerController.whenCompleteOnMainThread(future, callback) instead of blocking.
Common operations on any Cooldown:
| Method | Effect |
|---|---|
startWith(seconds) / startWith(value, TimeUnit)
|
set the duration and start now |
start() |
(re)anchor the start at now, keeping the current duration |
stop() |
clear the cooldown (see semantics below) |
setDuration(millis) |
change the nominal duration |
setPersist(boolean) |
whether it outlives the process / reaches storage |
isInCooldown() |
true while time is still left on the stored duration |
isInCooldown(customWaitSeconds) |
ask against a different duration (e.g. a VIP early-access window) |
getTimeLeft() / getFCTimeFrame()
|
remaining time (millis / formatted) |
warnPlayer(sender) |
send the localized "you must wait N" message |
stop() is a tombstone, not a delete of nothing: it sets the start anchor to 0, clears the persist
flag, and stamps the mutation clock. If there was a stored row, the route is still told (to drop it) -
and on a network route the tombstone itself is written so peers see the stop rather than resurrecting the
old state.
CooldownEntry.timeStart is an absolute epoch anchor, and that is deliberate: a read can reinterpret
the same anchor against a duration of its own. Asking isInCooldown(150) on a cooldown that was started
for 300 s lets you, say, wave a VIP through at the halfway mark - something an entry that only stored the
expiry could never answer.
| Route | Lives in | Written | Convergence |
|---|---|---|---|
Cooldown.of |
this server's Cooldowns.yml
|
on mutation (async save) | none (single file) |
Cooldown.network |
ec_server_cooldowns on the shared network backend |
immediately on mutation | last-write-wins merge |
PlayerCooldown.of |
PlayerCooldownsLocal PDSection (loaded at login) |
on the flush tick, once dirtied | ADOPT_WINNER (per-player) |
PlayerCooldown.network |
PlayerCooldownsNetwork account section |
on the flush tick, once dirtied | last-write-wins merge |
The server-network collection is the admin's to rename. network.server-cooldowns.collection in
storage.yml is generated on first boot and exists so a name collision with another plugin has a way
out; cache: { policy: TTL, ttlSeconds: N } next to it bounds how stale another server's write may look
here when the backend has no change feed. NOCACHE is refused - the flush iterates the cached values,
so a route without a cache would lose every write. See
Storage Backends.
Last-write-wins convergence. Two servers mutating the same cooldown converge through
CooldownEntry.latest(a, b): newest updatedAt wins, with fixed tiebreakers (expiry, then start, then
persist) so latest(a, b) and latest(b, a) always settle on the same state. It is deliberately not
a max over expiry - that would resurrect a cooldown a newer stop() has already tombstoned.
Read-through / freshness (network routes). A cache miss is never answered as "no cooldown". The server-network route reads through to the backend first: a cell the cache-sync marked stale (because a peer just wrote that very cooldown) misses exactly like a never-seen id, and answering "free" there would be the cross-server bypass the route exists to prevent. So a miss costs a single point read, only for an id no row exists for yet or one a peer just wrote.
Aliases /eccooldown, /eccooldowns. Covers every quadrant of the matrix - set/setplayer take the
network reach through a --network/-n flag instead of a separate subcommand:
| Subcommand | Reach |
|---|---|
viewserver |
show a server (local) cooldown |
viewplayer <player> |
show a player's cooldowns |
set <id> <duration> [--network|-n] |
set a server cooldown - local by default, network with the flag |
setplayer <player> <id> <duration> [--network|-n] |
set a player cooldown - local by default, network with the flag |
reset <id> |
clear a server cooldown |
resetplayer <player> <id> |
clear a player cooldown |
reload |
reload the server cooldowns file |
/eccooldown set myid 5m -> server, local
/eccooldown set myid 5m --network -> server, network
/eccooldown setplayer Notch myid 5m -n -> player, network (short alias)
- PlayerData & PDSections - the per-player rows the local player route uses.
- Accounts - the account-shared rows the network player route uses.
- Storage Backends - where the network cooldown rows live.
-
Localization - the
warnPlayermessage and its placeholders.
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